summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Monk <jmonk@google.com>2017-10-19 09:30:56 -0400
committerJason Monk <jmonk@google.com>2017-10-19 09:30:56 -0400
commitd439404c9988df6001e4ff8bce31537e2692660e (patch)
treeb1462a7177b8a2791140964761eb49d173cdc878
parent93b7ee4fce01df52a6607f0b1965cbafdfeaf1a6 (diff)
downloadandroid-28-d439404c9988df6001e4ff8bce31537e2692660e.tar.gz
Import Android SDK Platform P [4402356]
/google/data/ro/projects/android/fetch_artifact \ --bid 4386628 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4402356.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: Ie49e24e1f4ae9dc96306111e953d3db1e1495b53
-rw-r--r--android/accessibilityservice/GestureDescription.java12
-rw-r--r--android/app/Activity.java8
-rw-r--r--android/app/ActivityManager.java294
-rw-r--r--android/app/ActivityOptions.java43
-rw-r--r--android/app/KeyguardManager.java2
-rw-r--r--android/app/NotificationChannel.java2
-rw-r--r--android/app/NotificationManager.java9
-rw-r--r--android/app/StatusBarManager.java5
-rw-r--r--android/app/TaskStackListener.java7
-rw-r--r--android/app/WallpaperManager.java31
-rw-r--r--android/app/WindowConfiguration.java3
-rw-r--r--android/app/assist/AssistStructure.java57
-rw-r--r--android/app/job/JobScheduler.java41
-rw-r--r--android/app/job/JobService.java102
-rw-r--r--android/app/slice/Slice.java (renamed from android/slice/Slice.java)154
-rw-r--r--android/app/slice/SliceItem.java (renamed from android/slice/SliceItem.java)26
-rw-r--r--android/app/slice/SliceProvider.java (renamed from android/slice/SliceProvider.java)38
-rw-r--r--android/app/slice/SliceQuery.java (renamed from android/slice/SliceQuery.java)15
-rw-r--r--android/app/slice/views/ActionRow.java (renamed from android/slice/views/ActionRow.java)8
-rw-r--r--android/app/slice/views/GridView.java (renamed from android/slice/views/GridView.java)18
-rw-r--r--android/app/slice/views/LargeSliceAdapter.java (renamed from android/slice/views/LargeSliceAdapter.java)10
-rw-r--r--android/app/slice/views/LargeTemplateView.java (renamed from android/slice/views/LargeTemplateView.java)15
-rw-r--r--android/app/slice/views/MessageView.java (renamed from android/slice/views/MessageView.java)10
-rw-r--r--android/app/slice/views/RemoteInputView.java (renamed from android/slice/views/RemoteInputView.java)2
-rw-r--r--android/app/slice/views/ShortcutView.java (renamed from android/slice/views/ShortcutView.java)10
-rw-r--r--android/app/slice/views/SliceView.java (renamed from android/slice/views/SliceView.java)18
-rw-r--r--android/app/slice/views/SliceViewUtil.java (renamed from android/slice/views/SliceViewUtil.java)2
-rw-r--r--android/app/slice/views/SmallTemplateView.java (renamed from android/slice/views/SmallTemplateView.java)24
-rw-r--r--android/app/usage/UsageStatsManager.java19
-rw-r--r--android/arch/lifecycle/ActivityFullLifecycleTest.java58
-rw-r--r--android/arch/lifecycle/AndroidViewModel.java7
-rw-r--r--android/arch/lifecycle/ClassesInfoCache.java16
-rw-r--r--android/arch/lifecycle/ComputableLiveData.java139
-rw-r--r--android/arch/lifecycle/DispatcherActivityCallbackTest.java77
-rw-r--r--android/arch/lifecycle/Lifecycle.java7
-rw-r--r--android/arch/lifecycle/LifecycleDispatcher.java182
-rw-r--r--android/arch/lifecycle/LifecycleOwner.java3
-rw-r--r--android/arch/lifecycle/LifecycleRegistry.java63
-rw-r--r--android/arch/lifecycle/LifecycleRegistryOwner.java3
-rw-r--r--android/arch/lifecycle/LifecycleRegistryTest.java19
-rw-r--r--android/arch/lifecycle/LiveData.java410
-rw-r--r--android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java177
-rw-r--r--android/arch/lifecycle/LiveDataTest.java224
-rw-r--r--android/arch/lifecycle/MediatorLiveData.java39
-rw-r--r--android/arch/lifecycle/MissingClassTest.java44
-rw-r--r--android/arch/lifecycle/PartiallyCoveredActivityTest.java200
-rw-r--r--android/arch/lifecycle/ProcessLifecycleOwner.java5
-rw-r--r--android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java (renamed from android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java)3
-rw-r--r--android/arch/lifecycle/ProcessOwnerTest.java24
-rw-r--r--android/arch/lifecycle/ReportFragment.java1
-rw-r--r--android/arch/lifecycle/TestUtils.java108
-rw-r--r--android/arch/lifecycle/Transformations.java8
-rw-r--r--android/arch/lifecycle/ViewModelProvider.java11
-rw-r--r--android/arch/lifecycle/ViewModelProviders.java3
-rw-r--r--android/arch/lifecycle/ViewModelStoreOwner.java3
-rw-r--r--android/arch/lifecycle/ViewModelStores.java5
-rw-r--r--android/arch/lifecycle/testapp/CollectingLifecycleOwner.java (renamed from android/arch/lifecycle/testapp/CollectingActivity.java)11
-rw-r--r--android/arch/lifecycle/testapp/CollectingSupportActivity.java113
-rw-r--r--android/arch/lifecycle/testapp/CollectingSupportFragment.java104
-rw-r--r--android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java32
-rw-r--r--android/arch/lifecycle/testapp/FullLifecycleTestActivity.java88
-rw-r--r--android/arch/lifecycle/testapp/MainActivity.java31
-rw-r--r--android/arch/lifecycle/testapp/NavigationDialogActivity.java6
-rw-r--r--android/arch/lifecycle/testapp/NonSupportActivity.java85
-rw-r--r--android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java95
-rw-r--r--android/arch/lifecycle/testapp/TestEvent.java4
-rw-r--r--android/arch/lifecycle/testapp/TestObserver.java2
-rw-r--r--android/arch/paging/BoundedDataSource.java2
-rw-r--r--android/arch/paging/ContiguousDataSource.java110
-rw-r--r--android/arch/paging/ContiguousPagedList.java408
-rw-r--r--android/arch/paging/ContiguousPagedListTest.java62
-rw-r--r--android/arch/paging/DataSource.java14
-rw-r--r--android/arch/paging/KeyedDataSource.java15
-rw-r--r--android/arch/paging/LivePagedListProvider.java132
-rw-r--r--android/arch/paging/NullPaddedList.java75
-rw-r--r--android/arch/paging/Page.java51
-rw-r--r--android/arch/paging/PageArrayList.java130
-rw-r--r--android/arch/paging/PageArrayListTest.java49
-rw-r--r--android/arch/paging/PageResult.java56
-rw-r--r--android/arch/paging/PagedList.java145
-rw-r--r--android/arch/paging/PagedListAdapterHelper.java109
-rw-r--r--android/arch/paging/PagedListAdapterHelperTest.java59
-rw-r--r--android/arch/paging/PagedStorage.java433
-rw-r--r--android/arch/paging/PagedStorageDiffHelper.java (renamed from android/arch/paging/ContiguousDiffHelper.java)65
-rw-r--r--android/arch/paging/PagedStorageDiffHelperTest.java (renamed from android/arch/paging/ContiguousDiffHelperTest.java)67
-rw-r--r--android/arch/paging/PositionalDataSource.java25
-rw-r--r--android/arch/paging/SnapshotPagedList.java64
-rw-r--r--android/arch/paging/SparseDiffHelper.java99
-rw-r--r--android/arch/paging/StringPagedList.java56
-rw-r--r--android/arch/paging/TestExecutor.java2
-rw-r--r--android/arch/paging/TiledDataSource.java58
-rw-r--r--android/arch/paging/TiledPagedList.java276
-rw-r--r--android/arch/paging/TiledPagedListTest.java124
-rw-r--r--android/arch/persistence/room/InvalidationTracker.java4
-rw-r--r--android/arch/persistence/room/Relation.java18
-rw-r--r--android/arch/persistence/room/Room.java2
-rw-r--r--android/arch/persistence/room/RoomDatabase.java15
-rw-r--r--android/arch/persistence/room/RoomWarnings.java8
-rw-r--r--android/arch/persistence/room/Transaction.java37
-rw-r--r--android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java1
-rw-r--r--android/arch/persistence/room/integration/testapp/database/CustomerDao.java2
-rw-r--r--android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java47
-rw-r--r--android/arch/persistence/room/integration/testapp/test/InvalidationTest.java120
-rw-r--r--android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java8
-rw-r--r--android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java471
-rw-r--r--android/arch/persistence/room/migration/Migration.java3
-rw-r--r--android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java5
-rw-r--r--android/arch/persistence/room/paging/LimitOffsetDataSource.java36
-rw-r--r--android/arch/persistence/room/util/StringUtil.java7
-rw-r--r--android/content/ContentProvider.java3
-rw-r--r--android/content/ContentResolver.java36
-rw-r--r--android/content/Intent.java52
-rw-r--r--android/content/IntentFilter.java63
-rw-r--r--android/content/pm/FeatureInfo.java13
-rw-r--r--android/content/pm/LauncherApps.java51
-rw-r--r--android/content/pm/PackageManagerInternal.java5
-rw-r--r--android/content/pm/PackageParser.java23
-rw-r--r--android/content/pm/PermissionInfo.java5
-rw-r--r--android/content/pm/ShortcutInfo.java207
-rw-r--r--android/content/pm/ShortcutServiceInternal.java7
-rw-r--r--android/content/res/ResourcesImpl.java2
-rw-r--r--android/database/SQLiteDatabaseIoPerfTest.java176
-rw-r--r--android/database/SQLiteDatabasePerfTest.java223
-rw-r--r--android/graphics/Bitmap.java2
-rw-r--r--android/graphics/BitmapFactory.java3
-rw-r--r--android/media/AudioAttributes.java23
-rw-r--r--android/media/MediaMetadataRetriever.java2
-rw-r--r--android/media/MediaRecorder.java6
-rw-r--r--android/media/tv/TvInputManager.java7
-rw-r--r--android/net/LinkProperties.java30
-rw-r--r--android/net/ip/ConnectivityPacketTracker.java23
-rw-r--r--android/net/ip/IpManager.java26
-rw-r--r--android/net/util/SharedLog.java4
-rw-r--r--android/os/BatteryStats.java757
-rw-r--r--android/os/Debug.java20
-rw-r--r--android/os/ParcelFileDescriptor.java4
-rw-r--r--android/os/PatternMatcher.java16
-rw-r--r--android/os/ServiceManager.java105
-rw-r--r--android/os/SystemProperties.java6
-rw-r--r--android/os/UserManager.java19
-rw-r--r--android/preference/SeekBarVolumizer.java3
-rw-r--r--android/provider/Settings.java25
-rw-r--r--android/service/autofill/AutofillService.java77
-rw-r--r--android/service/autofill/SaveInfo.java88
-rw-r--r--android/service/autofill/SaveRequest.java12
-rw-r--r--android/service/notification/ZenModeConfig.java79
-rw-r--r--android/support/LibraryVersions.java6
-rw-r--r--android/support/car/drawer/CarDrawerActivity.java152
-rw-r--r--android/support/car/drawer/CarDrawerAdapter.java182
-rw-r--r--android/support/car/drawer/CarDrawerController.java306
-rw-r--r--android/support/car/drawer/DrawerItemClickListener.java (renamed from android/util/StatsLogTag.java)25
-rw-r--r--android/support/car/drawer/DrawerItemViewHolder.java87
-rw-r--r--android/support/car/widget/PagedListView.java57
-rw-r--r--android/support/media/tv/BasePreviewProgram.java100
-rw-r--r--android/support/media/tv/BaseProgram.java23
-rw-r--r--android/support/media/tv/Program.java3
-rw-r--r--android/support/media/tv/TvContractCompat.java82
-rw-r--r--android/support/media/tv/WatchNextProgram.java27
-rw-r--r--android/support/mediacompat/testlib/IntentConstants.java2
-rw-r--r--android/support/mediacompat/testlib/MediaSessionConstants.java57
-rw-r--r--android/support/transition/AutoTransition.java6
-rw-r--r--android/support/v17/leanback/widget/GridLayoutManager.java18
-rw-r--r--android/support/v4/content/res/ResourcesCompat.java4
-rw-r--r--android/support/v4/media/RatingCompat.java23
-rw-r--r--android/support/v4/media/RatingCompatKitkat.java67
-rw-r--r--android/support/v4/provider/FontsContractCompat.java3
-rw-r--r--android/support/v7/app/MediaRouteButton.java3
-rw-r--r--android/support/v7/app/MediaRouteChooserDialog.java6
-rw-r--r--android/support/v7/app/MediaRouteControllerDialog.java8
-rw-r--r--android/support/v7/app/MediaRouterThemeHelper.java110
-rw-r--r--android/support/v7/util/DiffUtil.java67
-rw-r--r--android/support/v7/widget/AppCompatTextHelper.java37
-rw-r--r--android/support/v7/widget/TintTypedArray.java5
-rw-r--r--android/telephony/CarrierConfigManager.java34
-rw-r--r--android/telephony/MbmsDownloadSession.java41
-rw-r--r--android/telephony/NetworkScanRequest.java105
-rw-r--r--android/telephony/ServiceState.java9
-rw-r--r--android/telephony/mbms/DownloadRequest.java30
-rw-r--r--android/telephony/mbms/MbmsDownloadReceiver.java11
-rw-r--r--android/telephony/mbms/vendor/MbmsDownloadServiceBase.java18
-rw-r--r--android/telephony/mbms/vendor/MbmsStreamingServiceBase.java8
-rw-r--r--android/text/BoringLayoutCreateDrawPerfTest.java2
-rw-r--r--android/text/BoringLayoutIsBoringPerfTest.java2
-rw-r--r--android/text/DynamicLayout.java6
-rw-r--r--android/text/Hyphenator.java251
-rw-r--r--android/text/Layout.java14
-rw-r--r--android/text/PaintMeasureDrawPerfTest.java2
-rw-r--r--android/text/StaticLayout.java74
-rw-r--r--android/text/StaticLayoutCreateDrawPerfTest.java2
-rw-r--r--android/text/StaticLayout_Delegate.java26
-rw-r--r--android/text/TextLine.java10
-rw-r--r--android/text/TextViewSetTextMeasurePerfTest.java4
-rw-r--r--android/util/Log.java295
-rw-r--r--android/util/LruCache.java25
-rw-r--r--android/util/StatsLog.java76
-rw-r--r--android/util/StatsLogKey.java48
-rw-r--r--android/util/StatsLogValue.java54
-rw-r--r--android/view/SurfaceControl.java538
-rw-r--r--android/view/SurfaceView.java1135
-rw-r--r--android/view/View.java262
-rw-r--r--android/view/ViewDebug.java167
-rw-r--r--android/view/ViewGroup.java98
-rw-r--r--android/view/ViewRootImpl.java758
-rw-r--r--android/view/ViewStructure.java24
-rw-r--r--android/view/WindowManagerInternal.java4
-rw-r--r--android/view/WindowManagerPolicy.java7
-rw-r--r--android/view/accessibility/AccessibilityCache.java2
-rw-r--r--android/view/accessibility/AccessibilityManager.java916
-rw-r--r--android/view/autofill/AutofillManager.java149
-rw-r--r--android/view/textclassifier/TextClassifier.java8
-rw-r--r--android/view/textclassifier/TextClassifierConstants.java90
-rw-r--r--android/view/textclassifier/TextClassifierImpl.java14
-rw-r--r--android/view/textservice/TextServicesManager.java200
-rw-r--r--android/webkit/WebView.java2878
-rw-r--r--android/widget/Editor.java21
-rw-r--r--android/widget/RemoteViews.java6
-rw-r--r--android/widget/SelectionActionModeHelper.java30
-rw-r--r--android/widget/TextView.java41
-rw-r--r--com/android/commands/pm/Pm.java36
-rw-r--r--com/android/ex/photo/ActionBarWrapper.java3
-rw-r--r--com/android/ex/photo/PhotoViewActivity.java8
-rw-r--r--com/android/internal/alsa/AlsaCardsParser.java32
-rw-r--r--com/android/internal/alsa/AlsaDevicesParser.java30
-rw-r--r--com/android/internal/notification/SystemNotificationChannels.java13
-rw-r--r--com/android/internal/os/BatteryStatsImpl.java44
-rw-r--r--com/android/internal/os/LoggingPrintStream.java5
-rw-r--r--com/android/internal/os/ZygoteInit.java2
-rw-r--r--com/android/internal/telephony/CarrierKeyDownloadManager.java40
-rw-r--r--com/android/internal/telephony/NetworkScanRequestTracker.java28
-rw-r--r--com/android/internal/telephony/Phone.java4
-rw-r--r--com/android/internal/telephony/RIL.java134
-rw-r--r--com/android/internal/telephony/cat/CatService.java7
-rw-r--r--com/android/internal/telephony/uicc/UiccCardApplication.java5
-rw-r--r--com/android/internal/util/MemInfoReader.java4
-rw-r--r--com/android/internal/view/menu/ListMenuItemView.java8
-rw-r--r--com/android/internal/widget/Magnifier.java132
-rw-r--r--com/android/keyguard/CarrierText.java59
-rw-r--r--com/android/keyguard/KeyguardSecurityModel.java10
-rw-r--r--com/android/keyguard/KeyguardUpdateMonitor.java7
-rw-r--r--com/android/layoutlib/bridge/Bridge.java655
-rw-r--r--com/android/server/AppOpsService.java38
-rw-r--r--com/android/server/BatteryService.java421
-rw-r--r--com/android/server/IntentResolver.java27
-rw-r--r--com/android/server/SystemServer.java10
-rw-r--r--com/android/server/VibratorService.java24
-rw-r--r--com/android/server/accessibility/AccessibilityInputFilter.java20
-rw-r--r--com/android/server/accessibility/AutoclickController.java36
-rw-r--r--com/android/server/accessibility/BaseEventStreamTransformation.java31
-rw-r--r--com/android/server/accessibility/EventStreamTransformation.java37
-rw-r--r--com/android/server/accessibility/KeyboardInterceptor.java35
-rw-r--r--com/android/server/accessibility/MagnificationController.java19
-rw-r--r--com/android/server/accessibility/MagnificationGestureHandler.java382
-rw-r--r--com/android/server/accessibility/MotionEventInjector.java41
-rw-r--r--com/android/server/accessibility/TouchExplorer.java40
-rw-r--r--com/android/server/am/ActivityDisplay.java150
-rw-r--r--com/android/server/am/ActivityManagerDebugConfig.java8
-rw-r--r--com/android/server/am/ActivityManagerService.java640
-rw-r--r--com/android/server/am/ActivityManagerShellCommand.java100
-rw-r--r--com/android/server/am/ActivityMetricsLogger.java3
-rw-r--r--com/android/server/am/ActivityRecord.java72
-rw-r--r--com/android/server/am/ActivityStack.java215
-rw-r--r--com/android/server/am/ActivityStackSupervisor.java528
-rw-r--r--com/android/server/am/ActivityStarter.java133
-rw-r--r--com/android/server/am/AppTaskImpl.java150
-rw-r--r--com/android/server/am/BatteryStatsService.java25
-rw-r--r--com/android/server/am/BroadcastFilter.java24
-rw-r--r--com/android/server/am/BroadcastQueue.java52
-rw-r--r--com/android/server/am/BroadcastRecord.java11
-rw-r--r--com/android/server/am/KeyguardController.java10
-rw-r--r--com/android/server/am/LaunchingTaskPositioner.java211
-rw-r--r--com/android/server/am/LockTaskController.java10
-rw-r--r--com/android/server/am/ProcessRecord.java18
-rw-r--r--com/android/server/am/ReceiverList.java31
-rw-r--r--com/android/server/am/RecentTasks.java1040
-rw-r--r--com/android/server/am/ServiceRecord.java20
-rw-r--r--com/android/server/am/TaskPersister.java19
-rw-r--r--com/android/server/am/TaskRecord.java135
-rw-r--r--com/android/server/appwidget/AppWidgetServiceImpl.java49
-rw-r--r--com/android/server/audio/PlaybackActivityMonitor.java8
-rw-r--r--com/android/server/autofill/AutofillManagerServiceImpl.java34
-rw-r--r--com/android/server/autofill/Session.java43
-rw-r--r--com/android/server/backup/BackupManagerService.java67
-rw-r--r--com/android/server/backup/RefactoredBackupManagerService.java68
-rw-r--r--com/android/server/backup/Trampoline.java29
-rw-r--r--com/android/server/clipboard/ClipboardService.java3
-rw-r--r--com/android/server/connectivity/Tethering.java1
-rw-r--r--com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java52
-rw-r--r--com/android/server/display/AutomaticBrightnessController.java2
-rw-r--r--com/android/server/display/DisplayPowerController.java33
-rw-r--r--com/android/server/job/controllers/TimeController.java32
-rw-r--r--com/android/server/location/GnssLocationProvider.java6
-rw-r--r--com/android/server/media/MediaSessionRecord.java8
-rw-r--r--com/android/server/media/MediaSessionService.java36
-rw-r--r--com/android/server/notification/NotificationManagerService.java38
-rw-r--r--com/android/server/notification/ZenModeFiltering.java21
-rw-r--r--com/android/server/notification/ZenModeHelper.java24
-rw-r--r--com/android/server/pm/LauncherAppsService.java33
-rw-r--r--com/android/server/pm/PackageDexOptimizer.java17
-rw-r--r--com/android/server/pm/PackageInstallerSession.java11
-rw-r--r--com/android/server/pm/PackageManagerService.java317
-rw-r--r--com/android/server/pm/Settings.java29
-rw-r--r--com/android/server/pm/SharedUserSetting.java9
-rw-r--r--com/android/server/pm/ShortcutBitmapSaver.java10
-rw-r--r--com/android/server/pm/ShortcutLauncher.java53
-rw-r--r--com/android/server/pm/ShortcutPackage.java295
-rw-r--r--com/android/server/pm/ShortcutPackageInfo.java138
-rw-r--r--com/android/server/pm/ShortcutPackageItem.java46
-rw-r--r--com/android/server/pm/ShortcutParser.java6
-rw-r--r--com/android/server/pm/ShortcutRequestPinProcessor.java12
-rw-r--r--com/android/server/pm/ShortcutService.java153
-rw-r--r--com/android/server/pm/ShortcutUser.java3
-rw-r--r--com/android/server/pm/UserRestrictionsUtils.java2
-rw-r--r--com/android/server/pm/permission/BasePermission.java27
-rw-r--r--com/android/server/pm/permission/PermissionManagerInternal.java22
-rw-r--r--com/android/server/pm/permission/PermissionManagerService.java273
-rw-r--r--com/android/server/pm/permission/PermissionSettings.java117
-rw-r--r--com/android/server/policy/GlobalActions.java3
-rw-r--r--com/android/server/policy/PhoneWindowManager.java147
-rw-r--r--com/android/server/stats/StatsCompanionService.java136
-rw-r--r--com/android/server/statusbar/StatusBarManagerInternal.java1
-rw-r--r--com/android/server/statusbar/StatusBarManagerService.java7
-rw-r--r--com/android/server/tv/TvInputHardwareManager.java14
-rw-r--r--com/android/server/usb/UsbAlsaManager.java9
-rw-r--r--com/android/server/usb/UsbHostManager.java2
-rw-r--r--com/android/server/wifi/SelfRecovery.java2
-rw-r--r--com/android/server/wifi/WifiNative.java9
-rw-r--r--com/android/server/wifi/WifiStateMachine.java5
-rw-r--r--com/android/server/wifi/WifiStateMachinePrime.java3
-rw-r--r--com/android/server/wifi/WificondControl.java9
-rw-r--r--com/android/server/wifi/scanner/WificondScannerImpl.java16
-rw-r--r--com/android/server/wifi/util/InformationElementUtil.java4
-rw-r--r--com/android/server/wm/AppWindowToken.java5
-rw-r--r--com/android/server/wm/BlackFrame.java11
-rw-r--r--com/android/server/wm/CircularDisplayMask.java12
-rw-r--r--com/android/server/wm/ConfigurationContainer.java15
-rw-r--r--com/android/server/wm/DimLayer.java11
-rw-r--r--com/android/server/wm/DisplayContent.java337
-rw-r--r--com/android/server/wm/DockedStackDividerController.java23
-rw-r--r--com/android/server/wm/EmulatorDisplayOverlay.java11
-rw-r--r--com/android/server/wm/InputMonitor.java3
-rw-r--r--com/android/server/wm/PinnedStackController.java3
-rw-r--r--com/android/server/wm/RootWindowContainer.java11
-rw-r--r--com/android/server/wm/ScreenRotationAnimation.java17
-rw-r--r--com/android/server/wm/StackWindowController.java11
-rw-r--r--com/android/server/wm/Task.java16
-rw-r--r--com/android/server/wm/TaskPositioner.java3
-rw-r--r--com/android/server/wm/TaskSnapshotController.java4
-rw-r--r--com/android/server/wm/TaskStack.java93
-rw-r--r--com/android/server/wm/WallpaperController.java4
-rw-r--r--com/android/server/wm/WindowContainer.java4
-rw-r--r--com/android/server/wm/WindowManagerDebugConfig.java1
-rw-r--r--com/android/server/wm/WindowManagerService.java22
-rw-r--r--com/android/server/wm/WindowState.java48
-rw-r--r--com/android/server/wm/WindowStateAnimator.java11
-rw-r--r--com/android/server/wm/WindowSurfaceController.java259
-rw-r--r--com/android/server/wm/WindowSurfacePlacer.java3
-rw-r--r--com/android/settingslib/CustomEditTextPreference.java9
-rw-r--r--com/android/settingslib/development/AbstractLogdSizePreferenceController.java12
-rw-r--r--com/android/settingslib/development/AbstractLogpersistPreferenceController.java2
-rw-r--r--com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java85
-rw-r--r--com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java114
-rw-r--r--com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java95
-rw-r--r--com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java112
-rw-r--r--com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java119
-rw-r--r--com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java88
-rw-r--r--com/android/settingslib/suggestions/SuggestionParser.java2
-rw-r--r--com/android/settingslib/wifi/WifiTracker.java22
-rw-r--r--com/android/setupwizardlib/test/util/DrawingTestActivity.java9
-rw-r--r--com/android/setupwizardlib/util/WizardManagerHelper.java14
-rw-r--r--com/android/setupwizardlib/util/WizardManagerHelperTest.java8
-rw-r--r--com/android/setupwizardlib/view/NavigationBarButton.java127
-rw-r--r--com/android/setupwizardlib/view/RichTextView.java49
-rw-r--r--com/android/systemui/Dependency.java4
-rw-r--r--com/android/systemui/ImageWallpaper.java517
-rw-r--r--com/android/systemui/SwipeHelper.java62
-rw-r--r--com/android/systemui/doze/DozeUi.java3
-rw-r--r--com/android/systemui/globalactions/GlobalActionsComponent.java10
-rw-r--r--com/android/systemui/globalactions/GlobalActionsDialog.java49
-rw-r--r--com/android/systemui/globalactions/GlobalActionsImpl.java26
-rw-r--r--com/android/systemui/keyguard/WorkLockActivityController.java6
-rw-r--r--com/android/systemui/pip/phone/InputConsumerController.java2
-rw-r--r--com/android/systemui/pip/phone/PipManager.java5
-rw-r--r--com/android/systemui/pip/tv/PipManager.java5
-rw-r--r--com/android/systemui/plugins/GlobalActions.java3
-rw-r--r--com/android/systemui/power/PowerUI.java40
-rw-r--r--com/android/systemui/recents/Recents.java92
-rw-r--r--com/android/systemui/recents/RecentsActivity.java151
-rw-r--r--com/android/systemui/recents/RecentsActivityLaunchState.java20
-rw-r--r--com/android/systemui/recents/RecentsConfiguration.java34
-rw-r--r--com/android/systemui/recents/RecentsDebugFlags.java65
-rw-r--r--com/android/systemui/recents/RecentsImpl.java211
-rw-r--r--com/android/systemui/recents/RecentsSystemUser.java6
-rw-r--r--com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java2
-rw-r--r--com/android/systemui/recents/events/activity/IterateRecentsEvent.java27
-rw-r--r--com/android/systemui/recents/events/activity/LaunchTaskEvent.java2
-rw-r--r--com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java2
-rw-r--r--com/android/systemui/recents/events/activity/PackagesChangedEvent.java8
-rw-r--r--com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java2
-rw-r--r--com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java2
-rw-r--r--com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java2
-rw-r--r--com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java2
-rw-r--r--com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java4
-rw-r--r--com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java31
-rw-r--r--com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java2
-rw-r--r--com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java5
-rw-r--r--com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java2
-rw-r--r--com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java2
-rw-r--r--com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java2
-rw-r--r--com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java7
-rw-r--r--com/android/systemui/recents/misc/SystemServicesProxy.java740
-rw-r--r--com/android/systemui/recents/misc/TaskStackChangeListener.java65
-rw-r--r--com/android/systemui/recents/misc/TaskStackChangeListeners.java254
-rw-r--r--com/android/systemui/recents/model/RecentsPackageMonitor.java74
-rw-r--r--com/android/systemui/recents/model/RecentsTaskLoadPlan.java330
-rw-r--r--com/android/systemui/recents/model/TaskGrouping.java106
-rw-r--r--com/android/systemui/recents/model/TaskStack.java1140
-rw-r--r--com/android/systemui/recents/views/AnimateableViewBounds.java2
-rw-r--r--com/android/systemui/recents/views/DockState.java351
-rw-r--r--com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java170
-rw-r--r--com/android/systemui/recents/views/RecentsTransitionHelper.java57
-rw-r--r--com/android/systemui/recents/views/RecentsView.java141
-rw-r--r--com/android/systemui/recents/views/RecentsViewTouchHandler.java14
-rw-r--r--com/android/systemui/recents/views/SystemBarScrimViews.java4
-rw-r--r--com/android/systemui/recents/views/TaskStackAnimationHelper.java58
-rw-r--r--com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java199
-rw-r--r--com/android/systemui/recents/views/TaskStackView.java321
-rw-r--r--com/android/systemui/recents/views/TaskStackViewScroller.java4
-rw-r--r--com/android/systemui/recents/views/TaskStackViewTouchHandler.java19
-rw-r--r--com/android/systemui/recents/views/TaskView.java20
-rw-r--r--com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java24
-rw-r--r--com/android/systemui/recents/views/TaskViewHeader.java78
-rw-r--r--com/android/systemui/recents/views/TaskViewThumbnail.java18
-rw-r--r--com/android/systemui/recents/views/TaskViewTransform.java6
-rw-r--r--com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java5
-rw-r--r--com/android/systemui/recents/views/grid/TaskViewFocusFrame.java2
-rw-r--r--com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java4
-rw-r--r--com/android/systemui/shared/recents/model/BackgroundTaskLoader.java186
-rw-r--r--com/android/systemui/shared/recents/model/FilteredTaskList.java124
-rw-r--r--com/android/systemui/shared/recents/model/HighResThumbnailLoader.java (renamed from com/android/systemui/recents/model/HighResThumbnailLoader.java)19
-rw-r--r--com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java205
-rw-r--r--com/android/systemui/shared/recents/model/RecentsTaskLoader.java (renamed from com/android/systemui/recents/model/RecentsTaskLoader.java)372
-rw-r--r--com/android/systemui/shared/recents/model/Task.java (renamed from com/android/systemui/recents/model/Task.java)101
-rw-r--r--com/android/systemui/shared/recents/model/TaskFilter.java (renamed from com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java)15
-rw-r--r--com/android/systemui/shared/recents/model/TaskKeyCache.java (renamed from com/android/systemui/recents/model/TaskKeyCache.java)14
-rw-r--r--com/android/systemui/shared/recents/model/TaskKeyLruCache.java (renamed from com/android/systemui/recents/model/TaskKeyLruCache.java)8
-rw-r--r--com/android/systemui/shared/recents/model/TaskKeyStrongCache.java (renamed from com/android/systemui/recents/model/TaskKeyStrongCache.java)6
-rw-r--r--com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java60
-rw-r--r--com/android/systemui/shared/recents/model/TaskStack.java403
-rw-r--r--com/android/systemui/shared/recents/model/ThumbnailData.java (renamed from com/android/systemui/recents/model/ThumbnailData.java)31
-rw-r--r--com/android/systemui/shared/recents/utilities/AnimationProps.java (renamed from com/android/systemui/recents/views/AnimationProps.java)13
-rw-r--r--com/android/systemui/shared/recents/utilities/RectFEvaluator.java (renamed from com/android/systemui/recents/misc/RectFEvaluator.java)4
-rw-r--r--com/android/systemui/shared/recents/utilities/Utilities.java (renamed from com/android/systemui/recents/misc/Utilities.java)25
-rw-r--r--com/android/systemui/shared/system/ActivityManagerWrapper.java201
-rw-r--r--com/android/systemui/shared/system/PackageManagerWrapper.java50
-rw-r--r--com/android/systemui/shortcut/ShortcutKeyDispatcher.java29
-rw-r--r--com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java4
-rw-r--r--com/android/systemui/statusbar/ExpandableNotificationRow.java66
-rw-r--r--com/android/systemui/statusbar/car/CarStatusBar.java23
-rw-r--r--com/android/systemui/statusbar/phone/LockscreenWallpaper.java4
-rw-r--r--com/android/systemui/statusbar/phone/NavigationBarTransitions.java27
-rw-r--r--com/android/systemui/statusbar/phone/NotificationPanelView.java4
-rw-r--r--com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java19
-rw-r--r--com/android/systemui/statusbar/phone/StatusBar.java16
-rw-r--r--com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java20
-rw-r--r--com/android/systemui/util/wakelock/DelayedWakeLock.java2
-rw-r--r--foo/bar/ComplexDao.java464
-rw-r--r--foo/bar/ComplexDatabase.java118
-rw-r--r--foo/bar/DeletionDao.java271
-rw-r--r--foo/bar/UpdateDao.java280
-rw-r--r--foo/bar/WriterDao.java136
-rw-r--r--java/lang/Boolean.java2
-rw-r--r--java/lang/Byte.java2
-rw-r--r--java/lang/Character.java2
-rw-r--r--java/lang/Double.java2
-rw-r--r--java/lang/Float.java2
-rw-r--r--java/lang/Integer.java15
-rw-r--r--java/lang/Long.java6
-rw-r--r--java/lang/Short.java2
-rw-r--r--java/lang/String.java12
-rw-r--r--java/lang/Void.java17
-rw-r--r--java/lang/invoke/ArrayElementVarHandle.java (renamed from com/android/systemui/recents/misc/NamedCounter.java)30
-rw-r--r--java/lang/invoke/ByteArrayVarHandle.java37
-rw-r--r--java/lang/invoke/ByteBufferViewVarHandle.java38
-rw-r--r--java/lang/invoke/CallSite.java350
-rw-r--r--java/lang/invoke/FieldVarHandle.java46
-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.java410
-rw-r--r--java/net/Socket.java21
-rw-r--r--java/net/SocketException.java2
-rw-r--r--java/net/SocketImpl.java1
-rw-r--r--java/net/SocketInputStream.java13
-rw-r--r--java/net/SocketOutputStream.java15
-rw-r--r--java/net/SocksSocketImpl.java541
-rw-r--r--java/net/URLClassLoader.java18
-rw-r--r--java/security/AlgorithmParameters.java6
-rw-r--r--java/security/KeyFactory.java4
-rw-r--r--java/security/KeyPairGenerator.java4
-rw-r--r--java/security/MessageDigest.java6
-rw-r--r--java/security/Signature.java4
-rw-r--r--java/security/cert/CertificateFactory.java4
-rw-r--r--javax/crypto/KeyAgreement.java4
-rw-r--r--javax/crypto/KeyGenerator.java4
-rw-r--r--javax/crypto/Mac.java4
-rw-r--r--javax/crypto/SecretKeyFactory.java4
506 files changed, 21420 insertions, 24440 deletions
diff --git a/android/accessibilityservice/GestureDescription.java b/android/accessibilityservice/GestureDescription.java
index 92567d75..56f4ae2b 100644
--- a/android/accessibilityservice/GestureDescription.java
+++ b/android/accessibilityservice/GestureDescription.java
@@ -428,6 +428,18 @@ public final class GestureDescription {
}
@Override
+ public String toString() {
+ return "TouchPoint{"
+ + "mStrokeId=" + mStrokeId
+ + ", mContinuedStrokeId=" + mContinuedStrokeId
+ + ", mIsStartOfPath=" + mIsStartOfPath
+ + ", mIsEndOfPath=" + mIsEndOfPath
+ + ", mX=" + mX
+ + ", mY=" + mY
+ + '}';
+ }
+
+ @Override
public int describeContents() {
return 0;
}
diff --git a/android/app/Activity.java b/android/app/Activity.java
index e0ac9113..85f73bb7 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -542,9 +542,9 @@ import java.util.List;
* <ul>
* <li> <p>When creating a new document, the backing database entry or file for
* it is created immediately. For example, if the user chooses to write
- * a new e-mail, a new entry for that e-mail is created as soon as they
+ * a new email, a new entry for that email is created as soon as they
* start entering data, so that if they go to any other activity after
- * that point this e-mail will now appear in the list of drafts.</p>
+ * that point this email will now appear in the list of drafts.</p>
* <li> <p>When an activity's <code>onPause()</code> method is called, it should
* commit to the backing content provider or file any changes the user
* has made. This ensures that those changes will be seen by any other
@@ -1879,7 +1879,7 @@ public class Activity extends ContextThemeWrapper
if (isFinishing()) {
if (mAutoFillResetNeeded) {
- getAutofillManager().commit();
+ getAutofillManager().onActivityFinished();
} else if (mIntent != null
&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
// Activity was launched when user tapped a link in the Autofill Save UI - since
@@ -6259,6 +6259,8 @@ public class Activity extends ContextThemeWrapper
final AutofillManager afm = getAutofillManager();
if (afm != null) {
afm.dump(prefix, writer);
+ } else {
+ writer.print(prefix); writer.println("No AutofillManager");
}
}
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index 5e61727f..fc4c8d7f 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -16,14 +16,8 @@
package android.app;
-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.Manifest;
+import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -670,138 +664,6 @@ public class ActivityManager {
/** Invalid stack ID. */
public static final int INVALID_STACK_ID = -1;
- /** First static stack ID.
- * @hide */
- private static final int FIRST_STATIC_STACK_ID = 0;
-
- /** 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.
- * @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.
- * @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.
- * @hide */
- public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
-
- /** Last static stack stack ID.
- * @hide */
- private static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;
-
- /** Start of ID range used by stacks that are created dynamically.
- * @hide */
- public static final int FIRST_DYNAMIC_STACK_ID = LAST_STATIC_STACK_ID + 1;
-
- // TODO: Figure-out a way to remove this.
- /** @hide */
- public static boolean isStaticStack(int stackId) {
- return stackId >= FIRST_STATIC_STACK_ID && stackId <= LAST_STATIC_STACK_ID;
- }
-
- // TODO: It seems this mostly means a stack on a secondary display now. Need to see if
- // there are other meanings. If not why not just use information from the display?
- /** @hide */
- public static boolean isDynamicStack(int stackId) {
- return stackId >= FIRST_DYNAMIC_STACK_ID;
- }
-
- /**
- * Returns true if we try to maintain focus in the current stack when the top activity
- * finishes.
- * @hide
- */
- // TODO: Figure-out a way to remove. Probably isn't needed in the new world...
- public static boolean keepFocusInStackIfPossible(int stackId) {
- return stackId == FREEFORM_WORKSPACE_STACK_ID
- || stackId == DOCKED_STACK_ID || stackId == PINNED_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.
- * @hide
- */
- public static boolean replaceWindowsOnTaskMove(int sourceStackId, int targetStackId) {
- return sourceStackId == FREEFORM_WORKSPACE_STACK_ID
- || targetStackId == FREEFORM_WORKSPACE_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
- */
- public static boolean allowTopTaskToReturnHome(int stackId) {
- return stackId != PINNED_STACK_ID;
- }
-
- /**
- * Returns true if the stack should be resized to match the bounds specified by
- * {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack.
- * @hide
- */
- public static boolean resizeStackWithLaunchBounds(int stackId) {
- return stackId == PINNED_STACK_ID;
- }
-
- /**
- * Returns true if a window from the specified stack with {@param stackId} are normally
- * fullscreen, i. e. they can become the top opaque fullscreen window, meaning that it
- * controls system bars, lockscreen occluded/dismissing state, screen rotation animation,
- * etc.
- * @hide
- */
- // TODO: What about the other side of docked stack if we move this to WindowConfiguration?
- public static boolean normallyFullscreenWindows(int stackId) {
- return stackId != PINNED_STACK_ID && stackId != FREEFORM_WORKSPACE_STACK_ID
- && stackId != DOCKED_STACK_ID;
- }
-
- /** Returns the stack id for the input windowing mode.
- * @hide */
- // 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.
- * @hide */
- // TODO: To be removed once we are not using stack id for stuff...
- public static int getWindowingModeForStackId(int stackId, boolean inSplitScreenMode) {
- final int windowingMode;
- switch (stackId) {
- case FULLSCREEN_WORKSPACE_STACK_ID:
- windowingMode = inSplitScreenMode
- ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_FULLSCREEN;
- break;
- case PINNED_STACK_ID:
- windowingMode = WINDOWING_MODE_PINNED;
- break;
- case DOCKED_STACK_ID:
- windowingMode = WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
- break;
- case FREEFORM_WORKSPACE_STACK_ID:
- windowingMode = WINDOWING_MODE_FREEFORM;
- break;
- default :
- windowingMode = WINDOWING_MODE_UNDEFINED;
- }
- return windowingMode;
- }
}
/**
@@ -1080,11 +942,14 @@ public class ActivityManager {
ATTR_TASKDESCRIPTION_PREFIX + "color";
private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND =
ATTR_TASKDESCRIPTION_PREFIX + "colorBackground";
- private static final String ATTR_TASKDESCRIPTIONICONFILENAME =
+ private static final String ATTR_TASKDESCRIPTIONICON_FILENAME =
ATTR_TASKDESCRIPTION_PREFIX + "icon_filename";
+ private static final String ATTR_TASKDESCRIPTIONICON_RESOURCE =
+ ATTR_TASKDESCRIPTION_PREFIX + "icon_resource";
private String mLabel;
private Bitmap mIcon;
+ private int mIconRes;
private String mIconFilename;
private int mColorPrimary;
private int mColorBackground;
@@ -1098,9 +963,27 @@ public class ActivityManager {
* @param icon An icon that represents the current state of this task.
* @param colorPrimary A color to override the theme's primary color. This color must be
* opaque.
+ * @deprecated use TaskDescription constructor with icon resource instead
*/
+ @Deprecated
public TaskDescription(String label, Bitmap icon, int colorPrimary) {
- this(label, icon, null, colorPrimary, 0, 0, 0);
+ this(label, icon, 0, null, colorPrimary, 0, 0, 0);
+ if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
+ throw new RuntimeException("A TaskDescription's primary color should be opaque");
+ }
+ }
+
+ /**
+ * Creates the TaskDescription to the specified values.
+ *
+ * @param label A label and description of the current state of this task.
+ * @param iconRes A drawable resource of an icon that represents the current state of this
+ * activity.
+ * @param colorPrimary A color to override the theme's primary color. This color must be
+ * opaque.
+ */
+ public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
+ this(label, null, iconRes, null, colorPrimary, 0, 0, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -1111,9 +994,22 @@ public class ActivityManager {
*
* @param label A label and description of the current state of this activity.
* @param icon An icon that represents the current state of this activity.
+ * @deprecated use TaskDescription constructor with icon resource instead
*/
+ @Deprecated
public TaskDescription(String label, Bitmap icon) {
- this(label, icon, null, 0, 0, 0, 0);
+ this(label, icon, 0, null, 0, 0, 0, 0);
+ }
+
+ /**
+ * Creates the TaskDescription to the specified values.
+ *
+ * @param label A label and description of the current state of this activity.
+ * @param iconRes A drawable resource of an icon that represents the current state of this
+ * activity.
+ */
+ public TaskDescription(String label, @DrawableRes int iconRes) {
+ this(label, null, iconRes, null, 0, 0, 0, 0);
}
/**
@@ -1122,21 +1018,22 @@ public class ActivityManager {
* @param label A label and description of the current state of this activity.
*/
public TaskDescription(String label) {
- this(label, null, null, 0, 0, 0, 0);
+ this(label, null, 0, null, 0, 0, 0, 0);
}
/**
* Creates an empty TaskDescription.
*/
public TaskDescription() {
- this(null, null, null, 0, 0, 0, 0);
+ this(null, null, 0, null, 0, 0, 0, 0);
}
/** @hide */
- public TaskDescription(String label, Bitmap icon, String iconFilename, int colorPrimary,
- int colorBackground, int statusBarColor, int navigationBarColor) {
+ public TaskDescription(String label, Bitmap bitmap, int iconRes, String iconFilename,
+ int colorPrimary, int colorBackground, int statusBarColor, int navigationBarColor) {
mLabel = label;
- mIcon = icon;
+ mIcon = bitmap;
+ mIconRes = iconRes;
mIconFilename = iconFilename;
mColorPrimary = colorPrimary;
mColorBackground = colorBackground;
@@ -1158,6 +1055,7 @@ public class ActivityManager {
public void copyFrom(TaskDescription other) {
mLabel = other.mLabel;
mIcon = other.mIcon;
+ mIconRes = other.mIconRes;
mIconFilename = other.mIconFilename;
mColorPrimary = other.mColorPrimary;
mColorBackground = other.mColorBackground;
@@ -1173,6 +1071,7 @@ public class ActivityManager {
public void copyFromPreserveHiddenFields(TaskDescription other) {
mLabel = other.mLabel;
mIcon = other.mIcon;
+ mIconRes = other.mIconRes;
mIconFilename = other.mIconFilename;
mColorPrimary = other.mColorPrimary;
if (other.mColorBackground != 0) {
@@ -1245,6 +1144,14 @@ public class ActivityManager {
}
/**
+ * Sets the icon resource for this task description.
+ * @hide
+ */
+ public void setIcon(int iconRes) {
+ mIconRes = iconRes;
+ }
+
+ /**
* Moves the icon bitmap reference from an actual Bitmap to a file containing the
* bitmap.
* @hide
@@ -1272,6 +1179,13 @@ public class ActivityManager {
}
/** @hide */
+ @TestApi
+ public int getIconResource() {
+ return mIconRes;
+ }
+
+ /** @hide */
+ @TestApi
public String getIconFilename() {
return mIconFilename;
}
@@ -1337,7 +1251,10 @@ public class ActivityManager {
Integer.toHexString(mColorBackground));
}
if (mIconFilename != null) {
- out.attribute(null, ATTR_TASKDESCRIPTIONICONFILENAME, mIconFilename);
+ out.attribute(null, ATTR_TASKDESCRIPTIONICON_FILENAME, mIconFilename);
+ }
+ if (mIconRes != 0) {
+ out.attribute(null, ATTR_TASKDESCRIPTIONICON_RESOURCE, Integer.toString(mIconRes));
}
}
@@ -1349,8 +1266,10 @@ public class ActivityManager {
setPrimaryColor((int) Long.parseLong(attrValue, 16));
} else if (ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND.equals(attrName)) {
setBackgroundColor((int) Long.parseLong(attrValue, 16));
- } else if (ATTR_TASKDESCRIPTIONICONFILENAME.equals(attrName)) {
+ } else if (ATTR_TASKDESCRIPTIONICON_FILENAME.equals(attrName)) {
setIconFilename(attrValue);
+ } else if (ATTR_TASKDESCRIPTIONICON_RESOURCE.equals(attrName)) {
+ setIcon(Integer.parseInt(attrValue, 10));
}
}
@@ -1373,6 +1292,7 @@ public class ActivityManager {
dest.writeInt(1);
mIcon.writeToParcel(dest, 0);
}
+ dest.writeInt(mIconRes);
dest.writeInt(mColorPrimary);
dest.writeInt(mColorBackground);
dest.writeInt(mStatusBarColor);
@@ -1388,6 +1308,7 @@ public class ActivityManager {
public void readFromParcel(Parcel source) {
mLabel = source.readInt() > 0 ? source.readString() : null;
mIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
+ mIconRes = source.readInt();
mColorPrimary = source.readInt();
mColorBackground = source.readInt();
mStatusBarColor = source.readInt();
@@ -1408,8 +1329,8 @@ public class ActivityManager {
@Override
public String toString() {
return "TaskDescription Label: " + mLabel + " Icon: " + mIcon +
- " IconFilename: " + mIconFilename + " colorPrimary: " + mColorPrimary +
- " colorBackground: " + mColorBackground +
+ " IconRes: " + mIconRes + " IconFilename: " + mIconFilename +
+ " colorPrimary: " + mColorPrimary + " colorBackground: " + mColorBackground +
" statusBarColor: " + mColorBackground +
" navigationBarColor: " + mNavigationBarColor;
}
@@ -1571,7 +1492,6 @@ public class ActivityManager {
}
dest.writeInt(stackId);
dest.writeInt(userId);
- dest.writeLong(firstActiveTime);
dest.writeLong(lastActiveTime);
dest.writeInt(affiliatedTaskId);
dest.writeInt(affiliatedTaskColor);
@@ -1600,7 +1520,6 @@ public class ActivityManager {
TaskDescription.CREATOR.createFromParcel(source) : null;
stackId = source.readInt();
userId = source.readInt();
- firstActiveTime = source.readLong();
lastActiveTime = source.readLong();
affiliatedTaskId = source.readInt();
affiliatedTaskColor = source.readInt();
@@ -1643,31 +1562,6 @@ public class ActivityManager {
public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002;
/**
- * Provides a list that contains recent tasks for all
- * profiles of a user.
- * @hide
- */
- public static final int RECENT_INCLUDE_PROFILES = 0x0004;
-
- /**
- * Ignores all tasks that are on the home stack.
- * @hide
- */
- public static final int RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS = 0x0008;
-
- /**
- * Ignores the top task in the docked stack.
- * @hide
- */
- public static final int RECENT_INGORE_DOCKED_STACK_TOP_TASK = 0x0010;
-
- /**
- * Ignores all tasks that are on the pinned stack.
- * @hide
- */
- public static final int RECENT_INGORE_PINNED_STACK_TASKS = 0x0020;
-
- /**
* <p></p>Return a list of the tasks that the user has recently launched, with
* the most recent being first and older ones after in order.
*
@@ -1702,33 +1596,7 @@ public class ActivityManager {
public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
throws SecurityException {
try {
- return getService().getRecentTasks(maxNum,
- flags, UserHandle.myUserId()).getList();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Same as {@link #getRecentTasks(int, int)} but returns the recent tasks for a
- * specific user. It requires holding
- * the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
- * @param maxNum The maximum number of entries to return in the list. The
- * actual number returned may be smaller, depending on how many tasks the
- * user has started and the maximum number the system can remember.
- * @param flags Information about what to return. May be any combination
- * of {@link #RECENT_WITH_EXCLUDED} and {@link #RECENT_IGNORE_UNAVAILABLE}.
- *
- * @return Returns a list of RecentTaskInfo records describing each of
- * the recent tasks. Most recently activated tasks go first.
- *
- * @hide
- */
- public List<RecentTaskInfo> getRecentTasksForUser(int maxNum, int flags, int userId)
- throws SecurityException {
- try {
- return getService().getRecentTasks(maxNum,
- flags, userId).getList();
+ return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2021,22 +1889,6 @@ public class ActivityManager {
}
/**
- * Completely remove the given task.
- *
- * @param taskId Identifier of the task to be removed.
- * @return Returns true if the given task was found and removed.
- *
- * @hide
- */
- public boolean removeTask(int taskId) throws SecurityException {
- try {
- return getService().removeTask(taskId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* 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.
diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java
index a68c3a5c..b62e4c2d 100644
--- a/android/app/ActivityOptions.java
+++ b/android/app/ActivityOptions.java
@@ -17,13 +17,13 @@
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;
import android.annotation.TestApi;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -159,6 +159,12 @@ public class ActivityOptions {
private static final String KEY_ANIM_SPECS = "android:activity.animSpecs";
/**
+ * Whether the activity should be launched into LockTask mode.
+ * @see #setLockTaskMode(boolean)
+ */
+ private static final String KEY_LOCK_TASK_MODE = "android:activity.lockTaskMode";
+
+ /**
* The display id the activity should be launched into.
* @see #setLaunchDisplayId(int)
* @hide
@@ -279,6 +285,7 @@ public class ActivityOptions {
private int mResultCode;
private int mExitCoordinatorIndex;
private PendingIntent mUsageTimeReport;
+ private boolean mLockTaskMode = false;
private int mLaunchDisplayId = INVALID_DISPLAY;
@WindowConfiguration.WindowingMode
private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
@@ -870,6 +877,7 @@ public class ActivityOptions {
mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
break;
}
+ mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false);
mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
@@ -1056,6 +1064,37 @@ public class ActivityOptions {
}
/**
+ * Gets whether the activity is to be launched into LockTask mode.
+ * @return {@code true} if the activity is to be launched into LockTask mode.
+ * @see Activity#startLockTask()
+ * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
+ */
+ public boolean getLockTaskMode() {
+ return mLockTaskMode;
+ }
+
+ /**
+ * Sets whether the activity is to be launched into LockTask mode.
+ *
+ * Use this option to start an activity in LockTask mode. Note that only apps permitted by
+ * {@link android.app.admin.DevicePolicyManager} can run in LockTask mode. Therefore, if
+ * {@link android.app.admin.DevicePolicyManager#isLockTaskPermitted(String)} returns
+ * {@code false} for the package of the target activity, a {@link SecurityException} will be
+ * thrown during {@link Context#startActivity(Intent, Bundle)}.
+ *
+ * Defaults to {@code false} if not set.
+ *
+ * @param lockTaskMode {@code true} if the activity is to be launched into LockTask mode.
+ * @return {@code this} {@link ActivityOptions} instance.
+ * @see Activity#startLockTask()
+ * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
+ */
+ public ActivityOptions setLockTaskMode(boolean lockTaskMode) {
+ mLockTaskMode = lockTaskMode;
+ return this;
+ }
+
+ /**
* Gets the id of the display where activity should be launched.
* @return The id of the display where activity should be launched,
* {@link android.view.Display#INVALID_DISPLAY} if not set.
@@ -1248,6 +1287,7 @@ public class ActivityOptions {
mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
break;
}
+ mLockTaskMode = otherOptions.mLockTaskMode;
mAnimSpecs = otherOptions.mAnimSpecs;
mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
mSpecsFuture = otherOptions.mSpecsFuture;
@@ -1322,6 +1362,7 @@ public class ActivityOptions {
b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
break;
}
+ b.putBoolean(KEY_LOCK_TASK_MODE, mLockTaskMode);
b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId);
b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
b.putInt(KEY_LAUNCH_ACTIVITY_TYPE, mLaunchActivityType);
diff --git a/android/app/KeyguardManager.java b/android/app/KeyguardManager.java
index 54f74b15..1fe29004 100644
--- a/android/app/KeyguardManager.java
+++ b/android/app/KeyguardManager.java
@@ -387,8 +387,6 @@ public class KeyguardManager {
* such as the Home key and the right soft keys, don't work.
*
* @return true if in keyguard restricted input mode.
- *
- * @see android.view.WindowManagerPolicy#inKeyguardRestrictedKeyInputMode
*/
public boolean inKeyguardRestrictedInputMode() {
try {
diff --git a/android/app/NotificationChannel.java b/android/app/NotificationChannel.java
index 47063f08..c06ad3f3 100644
--- a/android/app/NotificationChannel.java
+++ b/android/app/NotificationChannel.java
@@ -32,6 +32,8 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.Preconditions;
+
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java
index eb52cb7f..a52dc1e4 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -934,8 +934,14 @@ public class NotificationManager {
public static final int PRIORITY_CATEGORY_CALLS = 1 << 3;
/** Calls from repeat callers are prioritized. */
public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 4;
+ /** Alarms are prioritized */
+ public static final int PRIORITY_CATEGORY_ALARMS = 1 << 5;
+ /** Media, system, game (catch-all for non-never suppressible sounds) are prioritized */
+ public static final int PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER = 1 << 6;
private static final int[] ALL_PRIORITY_CATEGORIES = {
+ PRIORITY_CATEGORY_ALARMS,
+ PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER,
PRIORITY_CATEGORY_REMINDERS,
PRIORITY_CATEGORY_EVENTS,
PRIORITY_CATEGORY_MESSAGES,
@@ -1135,6 +1141,9 @@ public class NotificationManager {
case PRIORITY_CATEGORY_MESSAGES: return "PRIORITY_CATEGORY_MESSAGES";
case PRIORITY_CATEGORY_CALLS: return "PRIORITY_CATEGORY_CALLS";
case PRIORITY_CATEGORY_REPEAT_CALLERS: return "PRIORITY_CATEGORY_REPEAT_CALLERS";
+ case PRIORITY_CATEGORY_ALARMS: return "PRIORITY_CATEGORY_ALARMS";
+ case PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER:
+ return "PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER";
default: return "PRIORITY_CATEGORY_UNKNOWN_" + priorityCategory;
}
}
diff --git a/android/app/StatusBarManager.java b/android/app/StatusBarManager.java
index 8987bc02..23c4166d 100644
--- a/android/app/StatusBarManager.java
+++ b/android/app/StatusBarManager.java
@@ -73,15 +73,16 @@ public class StatusBarManager {
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_GLOBAL_ACTIONS = 1 << 3;
public static final int DISABLE2_NONE = 0x00000000;
public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS | DISABLE2_SYSTEM_ICONS
- | DISABLE2_NOTIFICATION_SHADE;
+ | DISABLE2_NOTIFICATION_SHADE | DISABLE2_GLOBAL_ACTIONS;
@IntDef(flag = true,
value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS, DISABLE2_SYSTEM_ICONS,
- DISABLE2_NOTIFICATION_SHADE})
+ DISABLE2_NOTIFICATION_SHADE, DISABLE2_GLOBAL_ACTIONS})
@Retention(RetentionPolicy.SOURCE)
public @interface Disable2Flags {}
diff --git a/android/app/TaskStackListener.java b/android/app/TaskStackListener.java
index 402e2095..895d12a7 100644
--- a/android/app/TaskStackListener.java
+++ b/android/app/TaskStackListener.java
@@ -77,7 +77,7 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub {
}
@Override
- public void onTaskRemovalStarted(int taskId) {
+ public void onTaskRemovalStarted(int taskId) throws RemoteException {
}
@Override
@@ -91,11 +91,10 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub {
}
@Override
- public void onTaskProfileLocked(int taskId, int userId) {
+ public void onTaskProfileLocked(int taskId, int userId) throws RemoteException {
}
@Override
- public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
- throws RemoteException {
+ public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) throws RemoteException {
}
}
diff --git a/android/app/WallpaperManager.java b/android/app/WallpaperManager.java
index 942cc995..081bd814 100644
--- a/android/app/WallpaperManager.java
+++ b/android/app/WallpaperManager.java
@@ -388,11 +388,12 @@ public class WallpaperManager {
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
@SetWallpaperFlags int which) {
- return peekWallpaperBitmap(context, returnDefault, which, context.getUserId());
+ return peekWallpaperBitmap(context, returnDefault, which, context.getUserId(),
+ false /* hardware */);
}
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
- @SetWallpaperFlags int which, int userId) {
+ @SetWallpaperFlags int which, int userId, boolean hardware) {
if (mService != null) {
try {
if (!mService.isWallpaperSupported(context.getOpPackageName())) {
@@ -409,7 +410,7 @@ public class WallpaperManager {
mCachedWallpaper = null;
mCachedWallpaperUserId = 0;
try {
- mCachedWallpaper = getCurrentWallpaperLocked(context, userId);
+ mCachedWallpaper = getCurrentWallpaperLocked(context, userId, hardware);
mCachedWallpaperUserId = userId;
} catch (OutOfMemoryError e) {
Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
@@ -447,7 +448,7 @@ public class WallpaperManager {
}
}
- private Bitmap getCurrentWallpaperLocked(Context context, int userId) {
+ private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware) {
if (mService == null) {
Log.w(TAG, "WallpaperService not running");
return null;
@@ -460,6 +461,9 @@ public class WallpaperManager {
if (fd != null) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
+ if (hardware) {
+ options.inPreferredConfig = Bitmap.Config.HARDWARE;
+ }
return BitmapFactory.decodeFileDescriptor(
fd.getFileDescriptor(), null, options);
} catch (OutOfMemoryError e) {
@@ -814,12 +818,23 @@ public class WallpaperManager {
}
/**
- * Like {@link #getDrawable()} but returns a Bitmap.
+ * Like {@link #getDrawable()} but returns a Bitmap with default {@link Bitmap.Config}.
*
* @hide
*/
public Bitmap getBitmap() {
- return getBitmapAsUser(mContext.getUserId());
+ return getBitmap(false);
+ }
+
+ /**
+ * Like {@link #getDrawable()} but returns a Bitmap.
+ *
+ * @param hardware Asks for a hardware backed bitmap.
+ * @see Bitmap.Config#HARDWARE
+ * @hide
+ */
+ public Bitmap getBitmap(boolean hardware) {
+ return getBitmapAsUser(mContext.getUserId(), hardware);
}
/**
@@ -827,8 +842,8 @@ public class WallpaperManager {
*
* @hide
*/
- public Bitmap getBitmapAsUser(int userId) {
- return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId);
+ public Bitmap getBitmapAsUser(int userId, boolean hardware) {
+ return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware);
}
/**
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index 6b405384..251863ca 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -511,7 +511,8 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
}
- private static String windowingModeToString(@WindowingMode int windowingMode) {
+ /** @hide */
+ public static String windowingModeToString(@WindowingMode int windowingMode) {
switch (windowingMode) {
case WINDOWING_MODE_UNDEFINED: return "undefined";
case WINDOWING_MODE_FULLSCREEN: return "fullscreen";
diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java
index d9b7cd7e..e491a4f9 100644
--- a/android/app/assist/AssistStructure.java
+++ b/android/app/assist/AssistStructure.java
@@ -616,6 +616,9 @@ public class AssistStructure implements Parcelable {
CharSequence[] mAutofillOptions;
boolean mSanitized;
HtmlInfo mHtmlInfo;
+ int mMinEms = -1;
+ int mMaxEms = -1;
+ int mMaxLength = -1;
// POJO used to override some autofill-related values when the node is parcelized.
// Not written to parcel.
@@ -713,6 +716,9 @@ public class AssistStructure implements Parcelable {
if (p instanceof HtmlInfo) {
mHtmlInfo = (HtmlInfo) p;
}
+ mMinEms = in.readInt();
+ mMaxEms = in.readInt();
+ mMaxLength = in.readInt();
}
if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
mX = in.readInt();
@@ -876,6 +882,9 @@ public class AssistStructure implements Parcelable {
} else {
out.writeParcelable(null, 0);
}
+ out.writeInt(mMinEms);
+ out.writeInt(mMaxEms);
+ out.writeInt(mMaxLength);
}
if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
out.writeInt(mX);
@@ -1444,6 +1453,39 @@ public class AssistStructure implements Parcelable {
public ViewNode getChildAt(int index) {
return mChildren[index];
}
+
+ /**
+ * Returns the minimum width in ems of the text associated with this node, or {@code -1}
+ * if not supported by the node.
+ *
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+ * not for assist purposes.
+ */
+ public int getMinTextEms() {
+ return mMinEms;
+ }
+
+ /**
+ * Returns the maximum width in ems of the text associated with this node, or {@code -1}
+ * if not supported by the node.
+ *
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+ * not for assist purposes.
+ */
+ public int getMaxTextEms() {
+ return mMaxEms;
+ }
+
+ /**
+ * Returns the maximum length of the text associated with this node node, or {@code -1}
+ * if not supported by the node or not set.
+ *
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+ * not for assist purposes.
+ */
+ public int getMaxTextLength() {
+ return mMaxLength;
+ }
}
/**
@@ -1776,6 +1818,21 @@ public class AssistStructure implements Parcelable {
}
@Override
+ public void setMinTextEms(int minEms) {
+ mNode.mMinEms = minEms;
+ }
+
+ @Override
+ public void setMaxTextEms(int maxEms) {
+ mNode.mMaxEms = maxEms;
+ }
+
+ @Override
+ public void setMaxTextLength(int maxLength) {
+ mNode.mMaxLength = maxLength;
+ }
+
+ @Override
public void setDataIsSensitive(boolean sensitive) {
mNode.mSanitized = !sensitive;
}
diff --git a/android/app/job/JobScheduler.java b/android/app/job/JobScheduler.java
index 3868439f..0deb2e13 100644
--- a/android/app/job/JobScheduler.java
+++ b/android/app/job/JobScheduler.java
@@ -24,7 +24,6 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.ClipData;
import android.content.Context;
-import android.content.Intent;
import android.os.Bundle;
import android.os.PersistableBundle;
@@ -40,16 +39,18 @@ import java.util.List;
* and how to construct them. You will construct these JobInfo objects and pass them to the
* JobScheduler with {@link #schedule(JobInfo)}. When the criteria declared are met, the
* system will execute this job on your application's {@link android.app.job.JobService}.
- * You identify which JobService is meant to execute the logic for your job when you create the
- * JobInfo with
+ * You identify the service component that implements the logic for your job when you
+ * construct the JobInfo using
* {@link android.app.job.JobInfo.Builder#JobInfo.Builder(int,android.content.ComponentName)}.
* </p>
* <p>
- * The framework will be intelligent about when you receive your callbacks, and attempt to batch
- * and defer them as much as possible. Typically if you don't specify a deadline on your job, it
- * can be run at any moment depending on the current state of the JobScheduler's internal queue,
- * however it might be deferred as long as until the next time the device is connected to a power
- * source.
+ * The framework will be intelligent about when it executes jobs, and attempt to batch
+ * and defer them as much as possible. Typically if you don't specify a deadline on a job, it
+ * can be run at any moment depending on the current state of the JobScheduler's internal queue.
+ * <p>
+ * While a job is running, the system holds a wakelock on behalf of your app. For this reason,
+ * you do not need to take any action to guarantee that the device stays awake for the
+ * duration of the job.
* </p>
* <p>You do not
* instantiate this class directly; instead, retrieve it through
@@ -141,30 +142,34 @@ public abstract class JobScheduler {
int userId, String tag);
/**
- * Cancel a job that is pending in the JobScheduler.
- * @param jobId unique identifier for this job. Obtain this value from the jobs returned by
- * {@link #getAllPendingJobs()}.
+ * Cancel the specified job. If the job is currently executing, it is stopped
+ * immediately and the return value from its {@link JobService#onStopJob(JobParameters)}
+ * method is ignored.
+ *
+ * @param jobId unique identifier for the job to be canceled, as supplied to
+ * {@link JobInfo.Builder#JobInfo.Builder(int, android.content.ComponentName)
+ * JobInfo.Builder(int, android.content.ComponentName)}.
*/
public abstract void cancel(int jobId);
/**
- * Cancel all jobs that have been registered with the JobScheduler by this package.
+ * Cancel <em>all</em> jobs that have been scheduled by the calling application.
*/
public abstract void cancelAll();
/**
- * Retrieve all jobs for this package that are pending in the JobScheduler.
+ * Retrieve all jobs that have been scheduled by the calling application.
*
- * @return a list of all the jobs registered by this package that have not
- * yet been executed.
+ * @return a list of all of the app's scheduled jobs. This includes jobs that are
+ * currently started as well as those that are still waiting to run.
*/
public abstract @NonNull List<JobInfo> getAllPendingJobs();
/**
- * Retrieve a specific job for this package that is pending in the
- * JobScheduler.
+ * Look up the description of a scheduled job.
*
- * @return job registered by this package that has not yet been executed.
+ * @return The {@link JobInfo} description of the given scheduled job, or {@code null}
+ * if the supplied job ID does not correspond to any job.
*/
public abstract @Nullable JobInfo getPendingJob(int jobId);
}
diff --git a/android/app/job/JobService.java b/android/app/job/JobService.java
index 9096b47b..69afed20 100644
--- a/android/app/job/JobService.java
+++ b/android/app/job/JobService.java
@@ -18,16 +18,7 @@ package android.app.job;
import android.app.Service;
import android.content.Intent;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.lang.ref.WeakReference;
/**
* <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p>
@@ -55,7 +46,7 @@ public abstract class JobService extends Service {
* </pre>
*
* <p>If a job service is declared in the manifest but not protected with this
- * permission, that service will be ignored by the OS.
+ * permission, that service will be ignored by the system.
*/
public static final String PERMISSION_BIND =
"android.permission.BIND_JOB_SERVICE";
@@ -81,14 +72,36 @@ public abstract class JobService extends Service {
}
/**
- * Override this method with the callback logic for your job. Any such logic needs to be
- * performed on a separate thread, as this function is executed on your application's main
- * thread.
+ * Called to indicate that the job has begun executing. Override this method with the
+ * logic for your job. Like all other component lifecycle callbacks, this method executes
+ * on your application's main thread.
+ * <p>
+ * Return {@code true} from this method if your job needs to continue running. If you
+ * do this, the job remains active until you call
+ * {@link #jobFinished(JobParameters, boolean)} to tell the system that it has completed
+ * its work, or until the job's required constraints are no longer satisfied. For
+ * example, if the job was scheduled using
+ * {@link JobInfo.Builder#setRequiresCharging(boolean) setRequiresCharging(true)},
+ * it will be immediately halted by the system if the user unplugs the device from power,
+ * the job's {@link #onStopJob(JobParameters)} callback will be invoked, and the app
+ * will be expected to shut down all ongoing work connected with that job.
+ * <p>
+ * The system holds a wakelock on behalf of your app as long as your job is executing.
+ * This wakelock is acquired before this method is invoked, and is not released until either
+ * you call {@link #jobFinished(JobParameters, boolean)}, or after the system invokes
+ * {@link #onStopJob(JobParameters)} to notify your job that it is being shut down
+ * prematurely.
+ * <p>
+ * Returning {@code false} from this method means your job is already finished. The
+ * system's wakelock for the job will be released, and {@link #onStopJob(JobParameters)}
+ * will not be invoked.
*
- * @param params Parameters specifying info about this job, including the extras bundle you
- * optionally provided at job-creation time.
- * @return True if your service needs to process the work (on a separate thread). False if
- * there's no more work to be done for this job.
+ * @param params Parameters specifying info about this job, including the optional
+ * extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle).
+ * This object serves to identify this specific running job instance when calling
+ * {@link #jobFinished(JobParameters, boolean)}.
+ * @return {@code true} if your service will continue running, using a separate thread
+ * when appropriate. {@code false} means that this job has completed its work.
*/
public abstract boolean onStartJob(JobParameters params);
@@ -101,37 +114,44 @@ public abstract class JobService extends Service {
* {@link android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}, yet while your
* job was executing the user toggled WiFi. Another example is if you had specified
* {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
- * idle maintenance window. You are solely responsible for the behaviour of your application
- * upon receipt of this message; your app will likely start to misbehave if you ignore it. One
- * immediate repercussion is that the system will cease holding a wakelock for you.</p>
+ * idle maintenance window. You are solely responsible for the behavior of your application
+ * upon receipt of this message; your app will likely start to misbehave if you ignore it.
+ * <p>
+ * Once this method returns, the system releases the wakelock that it is holding on
+ * behalf of the job.</p>
*
- * @param params Parameters specifying info about this job.
- * @return True to indicate to the JobManager whether you'd like to reschedule this job based
- * on the retry criteria provided at job creation-time. False to drop the job. Regardless of
- * the value returned, your job must stop executing.
+ * @param params The parameters identifying this job, as supplied to
+ * the job in the {@link #onStartJob(JobParameters)} callback.
+ * @return {@code true} to indicate to the JobManager whether you'd like to reschedule
+ * this job based on the retry criteria provided at job creation-time; or {@code false}
+ * to end the job entirely. Regardless of the value returned, your job must stop executing.
*/
public abstract boolean onStopJob(JobParameters params);
/**
- * Call this to inform the JobManager you've finished executing. This can be called from any
- * thread, as it will ultimately be run on your application's main thread. When the system
- * receives this message it will release the wakelock being held.
+ * Call this to inform the JobScheduler that the job has finished its work. When the
+ * system receives this message, it releases the wakelock being held for the job.
* <p>
- * You can specify post-execution behaviour to the scheduler here with
- * <code>needsReschedule </code>. This will apply a back-off timer to your job based on
- * the default, or what was set with
- * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}. The original
- * requirements are always honoured even for a backed-off job. Note that a job running in
- * idle mode will not be backed-off. Instead what will happen is the job will be re-added
- * to the queue and re-executed within a future idle maintenance window.
+ * You can request that the job be scheduled again by passing {@code true} as
+ * the <code>wantsReschedule</code> parameter. This will apply back-off policy
+ * for the job; this policy can be adjusted through the
+ * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)} method
+ * when the job is originally scheduled. The job's initial
+ * requirements are preserved when jobs are rescheduled, regardless of backed-off
+ * policy.
+ * <p class="note">
+ * A job running while the device is dozing will not be rescheduled with the normal back-off
+ * policy. Instead, the job will be re-added to the queue and executed again during
+ * a future idle maintenance window.
* </p>
*
- * @param params Parameters specifying system-provided info about this job, this was given to
- * your application in {@link #onStartJob(JobParameters)}.
- * @param needsReschedule True if this job should be rescheduled according to the back-off
- * criteria specified at schedule-time. False otherwise.
+ * @param params The parameters identifying this job, as supplied to
+ * the job in the {@link #onStartJob(JobParameters)} callback.
+ * @param wantsReschedule {@code true} if this job should be rescheduled according
+ * to the back-off criteria specified when it was first scheduled; {@code false}
+ * otherwise.
*/
- public final void jobFinished(JobParameters params, boolean needsReschedule) {
- mEngine.jobFinished(params, needsReschedule);
+ public final void jobFinished(JobParameters params, boolean wantsReschedule) {
+ mEngine.jobFinished(params, wantsReschedule);
}
-} \ No newline at end of file
+}
diff --git a/android/slice/Slice.java b/android/app/slice/Slice.java
index 57686548..7f9f74b4 100644
--- a/android/slice/Slice.java
+++ b/android/app/slice/Slice.java
@@ -14,38 +14,35 @@
* 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;
+package android.app.slice;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.StringDef;
import android.app.PendingIntent;
import android.app.RemoteInput;
+import android.content.ContentResolver;
+import android.content.IContentProvider;
import android.graphics.drawable.Icon;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.widget.RemoteViews;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* 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 {
@@ -53,7 +50,7 @@ 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})
+ HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL})
public @interface SliceHint{ }
/**
@@ -102,6 +99,12 @@ public final class Slice implements Parcelable {
* Hint to indicate that this content should not be tinted.
*/
public static final String HINT_NO_TINT = "no_tint";
+ /**
+ * Hint to indicate that this slice is incomplete and an update will be sent once
+ * loading is complete. Slices which contain HINT_PARTIAL will not be cached by the
+ * OS and should not be cached by apps.
+ */
+ public static final String HINT_PARTIAL = "partial";
// These two are coming over from prototyping, but we probably don't want in
// public API, at least not right now.
@@ -109,19 +112,12 @@ public final class Slice implements Parcelable {
* @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) {
+ Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
mHints = hints;
mItems = items.toArray(new SliceItem[items.size()]);
mUri = uri;
@@ -147,15 +143,15 @@ public final class Slice implements Parcelable {
/**
* @return All child {@link SliceItem}s that this Slice contains.
*/
- public SliceItem[] getItems() {
- return mItems;
+ public List<SliceItem> getItems() {
+ return Arrays.asList(mItems);
}
/**
* @return All hints associated with this Slice.
*/
- public @SliceHint String[] getHints() {
- return mHints;
+ public @SliceHint List<String> getHints() {
+ return Arrays.asList(mHints);
}
/**
@@ -163,14 +159,14 @@ public final class Slice implements Parcelable {
*/
public SliceItem getPrimaryIcon() {
for (SliceItem item : getItems()) {
- if (item.getType() == TYPE_IMAGE) {
+ if (item.getType() == SliceItem.TYPE_IMAGE) {
return item;
}
- if (!(item.getType() == TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+ if (!(item.getType() == SliceItem.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);
+ && (item.getType() != SliceItem.TYPE_ACTION)) {
+ SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
if (icon != null) return icon;
}
}
@@ -235,10 +231,18 @@ public final class Slice implements Parcelable {
}
/**
+ * Add hints to the Slice being constructed
+ */
+ public Builder addHints(@SliceHint List<String> hints) {
+ return addHints(hints.toArray(new String[hints.size()]));
+ }
+
+ /**
* Add a sub-slice to the slice being constructed
*/
public Builder addSubSlice(@NonNull Slice slice) {
- mItems.add(new SliceItem(slice, TYPE_SLICE, slice.getHints()));
+ mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints().toArray(
+ new String[slice.getHints().size()])));
return this;
}
@@ -246,7 +250,7 @@ public final class Slice implements Parcelable {
* 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]));
+ mItems.add(new SliceItem(action, s, SliceItem.TYPE_ACTION, new String[0]));
return this;
}
@@ -254,31 +258,53 @@ public final class Slice implements Parcelable {
* Add text to the slice being constructed
*/
public Builder addText(CharSequence text, @SliceHint String... hints) {
- mItems.add(new SliceItem(text, TYPE_TEXT, hints));
+ mItems.add(new SliceItem(text, SliceItem.TYPE_TEXT, hints));
return this;
}
/**
+ * Add text to the slice being constructed
+ */
+ public Builder addText(CharSequence text, @SliceHint List<String> hints) {
+ return addText(text, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
* Add an image to the slice being constructed
*/
public Builder addIcon(Icon icon, @SliceHint String... hints) {
- mItems.add(new SliceItem(icon, TYPE_IMAGE, hints));
+ mItems.add(new SliceItem(icon, SliceItem.TYPE_IMAGE, hints));
return this;
}
/**
+ * Add an image to the slice being constructed
+ */
+ public Builder addIcon(Icon icon, @SliceHint List<String> hints) {
+ return addIcon(icon, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
* @hide This isn't final
*/
public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) {
- mItems.add(new SliceItem(remoteView, TYPE_REMOTE_VIEW, hints));
+ mItems.add(new SliceItem(remoteView, SliceItem.TYPE_REMOTE_VIEW, hints));
return this;
}
/**
* Add remote input to the slice being constructed
*/
+ public Slice.Builder addRemoteInput(RemoteInput remoteInput,
+ @SliceHint List<String> hints) {
+ return addRemoteInput(remoteInput, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
+ * 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));
+ mItems.add(new SliceItem(remoteInput, SliceItem.TYPE_REMOTE_INPUT, hints));
return this;
}
@@ -286,19 +312,33 @@ public final class Slice implements Parcelable {
* Add a color to the slice being constructed
*/
public Builder addColor(int color, @SliceHint String... hints) {
- mItems.add(new SliceItem(color, TYPE_COLOR, hints));
+ mItems.add(new SliceItem(color, SliceItem.TYPE_COLOR, hints));
return this;
}
/**
+ * Add a color to the slice being constructed
+ */
+ public Builder addColor(int color, @SliceHint List<String> hints) {
+ return addColor(color, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
* 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));
+ mItems.add(new SliceItem(time, SliceItem.TYPE_TIMESTAMP, hints));
return this;
}
/**
+ * Add a timestamp to the slice being constructed
+ */
+ public Slice.Builder addTimestamp(long time, @SliceHint List<String> hints) {
+ return addTimestamp(time, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
* Construct the slice.
*/
public Slice build() {
@@ -322,18 +362,18 @@ public final class Slice implements Parcelable {
* @hide
* @return A string representation of this slice.
*/
- public String getString() {
- return getString("");
+ public String toString() {
+ return toString("");
}
- private String getString(String indent) {
+ private String toString(String indent) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mItems.length; i++) {
sb.append(indent);
- if (mItems[i].getType() == TYPE_SLICE) {
+ if (mItems[i].getType() == SliceItem.TYPE_SLICE) {
sb.append("slice:\n");
- sb.append(mItems[i].getSlice().getString(indent + " "));
- } else if (mItems[i].getType() == TYPE_TEXT) {
+ sb.append(mItems[i].getSlice().toString(indent + " "));
+ } else if (mItems[i].getType() == SliceItem.TYPE_TEXT) {
sb.append("text: ");
sb.append(mItems[i].getText());
sb.append("\n");
@@ -344,4 +384,34 @@ public final class Slice implements Parcelable {
}
return sb.toString();
}
+
+ /**
+ * Turns a slice Uri into slice content.
+ *
+ * @param resolver ContentResolver to be used.
+ * @param uri The URI to a slice provider
+ * @return The Slice provided by the app or null if none is given.
+ * @see Slice
+ */
+ public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri) {
+ Preconditions.checkNotNull(uri, "uri");
+ IContentProvider provider = resolver.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(resolver.getPackageName(), 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 {
+ resolver.releaseProvider(provider);
+ }
+ }
}
diff --git a/android/slice/SliceItem.java b/android/app/slice/SliceItem.java
index 2827ab9d..6e69b051 100644
--- a/android/slice/SliceItem.java
+++ b/android/app/slice/SliceItem.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.slice;
+package android.app.slice;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -23,13 +23,15 @@ 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;
+import java.util.Arrays;
+import java.util.List;
+
/**
* A SliceItem is a single unit in the tree structure of a {@link Slice}.
@@ -47,7 +49,6 @@ import com.android.internal.util.ArrayUtils;
* 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 {
@@ -97,14 +98,15 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- protected @SliceHint String[] mHints;
+ protected @Slice.SliceHint
+ String[] mHints;
private final int mType;
private final Object mObj;
/**
* @hide
*/
- public SliceItem(Object obj, @SliceType int type, @SliceHint String[] hints) {
+ public SliceItem(Object obj, @SliceType int type, @Slice.SliceHint String[] hints) {
mHints = hints;
mType = type;
mObj = obj;
@@ -113,7 +115,7 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- public SliceItem(PendingIntent intent, Slice slice, int type, @SliceHint String[] hints) {
+ public SliceItem(PendingIntent intent, Slice slice, int type, @Slice.SliceHint String[] hints) {
this(new Pair<>(intent, slice), type, hints);
}
@@ -121,14 +123,14 @@ public final class SliceItem implements Parcelable {
* Gets all hints associated with this SliceItem.
* @return Array of hints.
*/
- public @NonNull @SliceHint String[] getHints() {
- return mHints;
+ public @NonNull @Slice.SliceHint List<String> getHints() {
+ return Arrays.asList(mHints);
}
/**
* @hide
*/
- public void addHint(@SliceHint String hint) {
+ public void addHint(@Slice.SliceHint String hint) {
mHints = ArrayUtils.appendElement(String.class, mHints, hint);
}
@@ -206,7 +208,7 @@ public final class SliceItem implements Parcelable {
* @param hint The hint to check for
* @return true if this item contains the given hint
*/
- public boolean hasHint(@SliceHint String hint) {
+ public boolean hasHint(@Slice.SliceHint String hint) {
return ArrayUtils.contains(mHints, hint);
}
@@ -234,7 +236,7 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- public boolean hasHints(@SliceHint String[] hints) {
+ public boolean hasHints(@Slice.SliceHint String[] hints) {
if (hints == null) return true;
for (String hint : hints) {
if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
@@ -247,7 +249,7 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- public boolean hasAnyHints(@SliceHint String[] hints) {
+ public boolean hasAnyHints(@Slice.SliceHint String[] hints) {
if (hints == null) return false;
for (String hint : hints) {
if (ArrayUtils.contains(mHints, hint)) {
diff --git a/android/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
index 4e21371b..df87b455 100644
--- a/android/slice/SliceProvider.java
+++ b/android/app/slice/SliceProvider.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.slice;
+package android.app.slice;
import android.Manifest.permission;
import android.content.ContentProvider;
@@ -26,6 +26,8 @@ import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
@@ -51,10 +53,15 @@ import java.util.concurrent.CountDownLatch;
* </pre>
*
* @see Slice
- * @hide
*/
public abstract class SliceProvider extends ContentProvider {
+ /**
+ * This is the Android platform's MIME type for a slice: URI
+ * containing a slice implemented through {@link SliceProvider}.
+ */
+ public static final String SLICE_TYPE = "vnd.android.slice";
+
private static final String TAG = "SliceProvider";
/**
* @hide
@@ -73,8 +80,18 @@ public abstract class SliceProvider extends ContentProvider {
/**
* Implemented to create a slice. Will be called on the main thread.
+ * <p>
+ * onBindSlice should return as quickly as possible so that the UI tied
+ * to this slice can be responsive. No network or other IO will be allowed
+ * during onBindSlice. Any loading that needs to be done should happen
+ * off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)}
+ * when the app is ready to provide the complete data in onBindSlice.
+ * <p>
+ *
* @see {@link Slice}.
+ * @see {@link Slice#HINT_PARTIAL}
*/
+ // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
public abstract Slice onBindSlice(Uri sliceUri);
@Override
@@ -120,11 +137,11 @@ public abstract class SliceProvider extends ContentProvider {
@Override
public final String getType(Uri uri) {
if (DEBUG) Log.d(TAG, "getType " + uri);
- return null;
+ return SLICE_TYPE;
}
@Override
- public final Bundle call(String method, String arg, Bundle extras) {
+ public Bundle call(String method, String arg, Bundle extras) {
if (method.equals(METHOD_SLICE)) {
getContext().enforceCallingPermission(permission.BIND_SLICE,
"Slice binding requires the permission BIND_SLICE");
@@ -143,8 +160,17 @@ public abstract class SliceProvider extends ContentProvider {
CountDownLatch latch = new CountDownLatch(1);
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(() -> {
- output[0] = onBindSlice(sliceUri);
- latch.countDown();
+ ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ try {
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyDeath()
+ .build());
+ output[0] = onBindSlice(sliceUri);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ latch.countDown();
+ }
});
try {
latch.await();
diff --git a/android/slice/SliceQuery.java b/android/app/slice/SliceQuery.java
index d99b26a5..d1fe2c90 100644
--- a/android/slice/SliceQuery.java
+++ b/android/app/slice/SliceQuery.java
@@ -14,12 +14,8 @@
* limitations under the License.
*/
-package android.slice;
+package android.app.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;
@@ -114,7 +110,9 @@ public class SliceQuery {
* @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);
+ List<String> h = s.getHints();
+ return find(new SliceItem(s, SliceItem.TYPE_SLICE, h.toArray(new String[h.size()])), type,
+ hints, nonHints);
}
/**
@@ -140,8 +138,9 @@ public class SliceQuery {
@Override
public SliceItem next() {
SliceItem item = items.poll();
- if (item.getType() == TYPE_SLICE || item.getType() == TYPE_ACTION) {
- items.addAll(Arrays.asList(item.getSlice().getItems()));
+ if (item.getType() == SliceItem.TYPE_SLICE
+ || item.getType() == SliceItem.TYPE_ACTION) {
+ items.addAll(item.getSlice().getItems());
}
return item;
}
diff --git a/android/slice/views/ActionRow.java b/android/app/slice/views/ActionRow.java
index 93e9c035..c7d99f7f 100644
--- a/android/slice/views/ActionRow.java
+++ b/android/app/slice/views/ActionRow.java
@@ -14,19 +14,19 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteInput;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
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;
diff --git a/android/slice/views/GridView.java b/android/app/slice/views/GridView.java
index 18a90f7d..6f30c507 100644
--- a/android/slice/views/GridView.java
+++ b/android/app/slice/views/GridView.java
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.views.LargeSliceAdapter.SliceListView;
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;
@@ -38,7 +38,7 @@ import android.widget.TextView;
import com.android.internal.R;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.List;
/**
* @hide
@@ -76,10 +76,10 @@ public class GridView extends LinearLayout implements SliceListView {
removeAllViews();
int total = 1;
if (slice.getType() == SliceItem.TYPE_SLICE) {
- SliceItem[] items = slice.getSlice().getItems();
- total = items.length;
+ List<SliceItem> items = slice.getSlice().getItems();
+ total = items.size();
for (int i = 0; i < total; i++) {
- SliceItem item = items[i];
+ SliceItem item = items.get(i);
if (isFull()) {
continue;
}
@@ -142,7 +142,7 @@ public class GridView extends LinearLayout implements SliceListView {
// 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.addAll(item.getSlice().getItems());
}
items.forEach(i -> {
Context context = getContext();
diff --git a/android/slice/views/LargeSliceAdapter.java b/android/app/slice/views/LargeSliceAdapter.java
index e77a1b2a..6794ff98 100644
--- a/android/slice/views/LargeSliceAdapter.java
+++ b/android/app/slice/views/LargeSliceAdapter.java
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.LargeSliceAdapter.SliceViewHolder;
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;
diff --git a/android/slice/views/LargeTemplateView.java b/android/app/slice/views/LargeTemplateView.java
index d53e8fcb..9e225162 100644
--- a/android/slice/views/LargeTemplateView.java
+++ b/android/app/slice/views/LargeTemplateView.java
@@ -14,22 +14,21 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.SliceView.SliceModeView;
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;
/**
@@ -86,7 +85,7 @@ public class LargeTemplateView extends SliceModeView {
if (slice.hasHint(Slice.HINT_LIST)) {
addList(slice, items);
} else {
- Arrays.asList(slice.getItems()).forEach(item -> {
+ slice.getItems().forEach(item -> {
if (item.hasHint(Slice.HINT_ACTIONS)) {
return;
} else if (item.getType() == SliceItem.TYPE_COLOR) {
@@ -109,7 +108,7 @@ public class LargeTemplateView extends SliceModeView {
}
private void addList(Slice slice, List<SliceItem> items) {
- List<SliceItem> sliceItems = Arrays.asList(slice.getItems());
+ List<SliceItem> sliceItems = slice.getItems();
sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
items.addAll(sliceItems);
}
diff --git a/android/slice/views/MessageView.java b/android/app/slice/views/MessageView.java
index 7b03e0bd..77252bf2 100644
--- a/android/slice/views/MessageView.java
+++ b/android/app/slice/views/MessageView.java
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.LargeSliceAdapter.SliceListView;
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;
diff --git a/android/slice/views/RemoteInputView.java b/android/app/slice/views/RemoteInputView.java
index a29bb5c0..e53cb1ea 100644
--- a/android/slice/views/RemoteInputView.java
+++ b/android/app/slice/views/RemoteInputView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import android.animation.Animator;
import android.app.Notification;
diff --git a/android/slice/views/ShortcutView.java b/android/app/slice/views/ShortcutView.java
index 8fe2f1ac..b6790c7d 100644
--- a/android/slice/views/ShortcutView.java
+++ b/android/app/slice/views/ShortcutView.java
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.SliceView.SliceModeView;
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;
diff --git a/android/slice/views/SliceView.java b/android/app/slice/views/SliceView.java
index f3792481..32484fca 100644
--- a/android/slice/views/SliceView.java
+++ b/android/app/slice/views/SliceView.java
@@ -14,23 +14,25 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import android.annotation.StringDef;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
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;
+import java.util.List;
+
/**
* A view that can display a {@link Slice} in different {@link SliceMode}'s.
*
@@ -120,7 +122,7 @@ public class SliceView extends LinearLayout {
*/
public void bindSlice(Uri sliceUri) {
validate(sliceUri);
- Slice s = mContext.getContentResolver().bindSlice(sliceUri);
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
bindSlice(s);
}
@@ -201,7 +203,7 @@ public class SliceView extends LinearLayout {
}
// TODO: Smarter mapping here from one state to the next.
SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
- SliceItem[] items = mCurrentSlice.getItems();
+ List<SliceItem> items = mCurrentSlice.getItems();
SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
Slice.HINT_ACTIONS,
Slice.HINT_ALT);
@@ -212,7 +214,7 @@ public class SliceView extends LinearLayout {
addView(mCurrentView);
addView(mActions);
}
- if (items.length > 1 || (items.length != 0 && items[0] != actionRow)) {
+ if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
mCurrentView.setVisibility(View.VISIBLE);
mCurrentView.setSlice(mCurrentSlice);
} else {
@@ -239,7 +241,7 @@ public class SliceView extends LinearLayout {
}
private static void validate(Uri sliceUri) {
- if (!ContentResolver.SCHEME_SLICE.equals(sliceUri.getScheme())) {
+ if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
throw new RuntimeException("Invalid uri " + sliceUri);
}
if (sliceUri.getPathSegments().size() == 0) {
diff --git a/android/slice/views/SliceViewUtil.java b/android/app/slice/views/SliceViewUtil.java
index 1b5a6d1e..19e8e7c9 100644
--- a/android/slice/views/SliceViewUtil.java
+++ b/android/app/slice/views/SliceViewUtil.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import android.annotation.ColorInt;
import android.content.Context;
diff --git a/android/slice/views/SmallTemplateView.java b/android/app/slice/views/SmallTemplateView.java
index b0b181ed..42b2d213 100644
--- a/android/slice/views/SmallTemplateView.java
+++ b/android/app/slice/views/SmallTemplateView.java
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package android.slice.views;
+package android.app.slice.views;
import android.app.PendingIntent.CanceledException;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.views.LargeSliceAdapter.SliceListView;
+import android.app.slice.views.SliceView.SliceModeView;
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;
@@ -34,7 +34,6 @@ 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;
@@ -117,7 +116,7 @@ public class SmallTemplateView extends SliceModeView implements SliceListView {
int itemCount = 0;
boolean hasSummary = false;
ArrayList<SliceItem> sliceItems = new ArrayList<SliceItem>(
- Arrays.asList(slice.getSlice().getItems()));
+ slice.getSlice().getItems());
for (int i = 0; i < sliceItems.size(); i++) {
SliceItem item = sliceItems.get(i);
if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
@@ -140,9 +139,9 @@ public class SmallTemplateView extends SliceModeView implements SliceListView {
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]);
+ List<SliceItem> subItems = item.getSlice().getItems();
+ for (int j = 0; j < subItems.size(); j++) {
+ sliceItems.add(subItems.get(j));
}
}
}
@@ -151,7 +150,8 @@ public class SmallTemplateView extends SliceModeView implements SliceListView {
@Override
public void setSlice(Slice slice) {
- setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints()));
+ setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE,
+ slice.getHints().toArray(new String[slice.getHints().size()])));
}
/**
diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java
index 051dccbd..fd579fce 100644
--- a/android/app/usage/UsageStatsManager.java
+++ b/android/app/usage/UsageStatsManager.java
@@ -48,10 +48,10 @@ import java.util.Map;
* </pre>
* A request for data in the middle of a time interval will include that interval.
* <p/>
- * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS, which
- * is a system-level permission and will not be granted to third-party apps. However, declaring
- * the permission implies intention to use the API and the user of the device can grant permission
- * through the Settings application.
+ * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS.
+ * However, declaring the permission implies intention to use the API and the user of the device
+ * still needs to grant permission through the Settings application.
+ * See {@link android.provider.Settings#ACTION_USAGE_ACCESS_SETTINGS}
*/
@SystemService(Context.USAGE_STATS_SERVICE)
public final class UsageStatsManager {
@@ -122,7 +122,7 @@ public final class UsageStatsManager {
* @param intervalType The time interval by which the stats are aggregated.
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
- * @return A list of {@link UsageStats} or null if none are available.
+ * @return A list of {@link UsageStats}
*
* @see #INTERVAL_DAILY
* @see #INTERVAL_WEEKLY
@@ -139,7 +139,7 @@ public final class UsageStatsManager {
return slice.getList();
}
} catch (RemoteException e) {
- // fallthrough and return null.
+ // fallthrough and return the empty list.
}
return Collections.emptyList();
}
@@ -152,7 +152,7 @@ public final class UsageStatsManager {
* @param intervalType The time interval by which the stats are aggregated.
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
- * @return A list of {@link ConfigurationStats} or null if none are available.
+ * @return A list of {@link ConfigurationStats}
*/
public List<ConfigurationStats> queryConfigurations(int intervalType, long beginTime,
long endTime) {
@@ -185,7 +185,7 @@ public final class UsageStatsManager {
return iter;
}
} catch (RemoteException e) {
- // fallthrough and return null
+ // fallthrough and return empty result.
}
return sEmptyResults;
}
@@ -197,8 +197,7 @@ public final class UsageStatsManager {
*
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
- * @return A {@link java.util.Map} keyed by package name, or null if no stats are
- * available.
+ * @return A {@link java.util.Map} keyed by package name
*/
public Map<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) {
List<UsageStats> stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime);
diff --git a/android/arch/lifecycle/ActivityFullLifecycleTest.java b/android/arch/lifecycle/ActivityFullLifecycleTest.java
index ee4e661a..78dd0150 100644
--- a/android/arch/lifecycle/ActivityFullLifecycleTest.java
+++ b/android/arch/lifecycle/ActivityFullLifecycleTest.java
@@ -16,48 +16,43 @@
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.testapp.TestEvent.ACTIVITY_CALLBACK;
-import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.START;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP;
+import static android.arch.lifecycle.TestUtils.flatMap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.testapp.CollectingActivity;
+import android.arch.lifecycle.testapp.CollectingLifecycleOwner;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
import android.arch.lifecycle.testapp.FrameworkLifecycleRegistryActivity;
-import android.arch.lifecycle.testapp.FullLifecycleTestActivity;
-import android.arch.lifecycle.testapp.SupportLifecycleRegistryActivity;
import android.arch.lifecycle.testapp.TestEvent;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
-import android.util.Pair;
+import android.support.v4.util.Pair;
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.List;
@SmallTest
@RunWith(Parameterized.class)
public class ActivityFullLifecycleTest {
@Rule
- public ActivityTestRule activityTestRule =
- new ActivityTestRule<>(FullLifecycleTestActivity.class);
+ public final ActivityTestRule<? extends CollectingLifecycleOwner> activityTestRule;
@Parameterized.Parameters
public static Class[] params() {
- return new Class[]{FullLifecycleTestActivity.class,
- SupportLifecycleRegistryActivity.class,
+ return new Class[]{CollectingSupportActivity.class,
FrameworkLifecycleRegistryActivity.class};
}
@@ -68,28 +63,13 @@ public class ActivityFullLifecycleTest {
@Test
- public void testFullLifecycle() throws InterruptedException {
- Activity activity = activityTestRule.getActivity();
- List<Pair<TestEvent, Event>> results = ((CollectingActivity) activity)
- .waitForCollectedEvents();
+ public void testFullLifecycle() throws Throwable {
+ CollectingLifecycleOwner owner = activityTestRule.getActivity();
+ TestUtils.waitTillResumed(owner, activityTestRule);
+ activityTestRule.finishActivity();
- Event[] expectedEvents =
- new Event[]{ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY};
-
- List<Pair<TestEvent, Event>> expected = new ArrayList<>();
- boolean beforeResume = true;
- for (Event i : expectedEvents) {
- if (beforeResume) {
- expected.add(new Pair<>(ACTIVITY_CALLBACK, i));
- expected.add(new Pair<>(LIFECYCLE_EVENT, i));
- } else {
- expected.add(new Pair<>(LIFECYCLE_EVENT, i));
- expected.add(new Pair<>(ACTIVITY_CALLBACK, i));
- }
- if (i == ON_RESUME) {
- beforeResume = false;
- }
- }
- assertThat(results, is(expected));
+ TestUtils.waitTillDestroyed(owner, activityTestRule);
+ List<Pair<TestEvent, Event>> results = owner.copyCollectedEvents();
+ assertThat(results, is(flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY)));
}
}
diff --git a/android/arch/lifecycle/AndroidViewModel.java b/android/arch/lifecycle/AndroidViewModel.java
index 2c7e1739..106b2ef0 100644
--- a/android/arch/lifecycle/AndroidViewModel.java
+++ b/android/arch/lifecycle/AndroidViewModel.java
@@ -16,7 +16,9 @@
package android.arch.lifecycle;
+import android.annotation.SuppressLint;
import android.app.Application;
+import android.support.annotation.NonNull;
/**
* Application context aware {@link ViewModel}.
@@ -25,16 +27,19 @@ import android.app.Application;
* <p>
*/
public class AndroidViewModel extends ViewModel {
+ @SuppressLint("StaticFieldLeak")
private Application mApplication;
- public AndroidViewModel(Application application) {
+ public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}
/**
* Return the application.
*/
+ @NonNull
public <T extends Application> T getApplication() {
+ //noinspection unchecked
return (T) mApplication;
}
}
diff --git a/android/arch/lifecycle/ClassesInfoCache.java b/android/arch/lifecycle/ClassesInfoCache.java
index f077daed..d88e2762 100644
--- a/android/arch/lifecycle/ClassesInfoCache.java
+++ b/android/arch/lifecycle/ClassesInfoCache.java
@@ -46,7 +46,7 @@ class ClassesInfoCache {
return mHasLifecycleMethods.get(klass);
}
- Method[] methods = klass.getDeclaredMethods();
+ Method[] methods = getDeclaredMethods(klass);
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
if (annotation != null) {
@@ -64,6 +64,18 @@ class ClassesInfoCache {
return false;
}
+ private Method[] getDeclaredMethods(Class klass) {
+ try {
+ return klass.getDeclaredMethods();
+ } catch (NoClassDefFoundError e) {
+ throw new IllegalArgumentException("The observer class has some methods that use "
+ + "newer APIs which are not available in the current OS version. Lifecycles "
+ + "cannot access even other methods so you should make sure that your "
+ + "observer classes only access framework classes that are available "
+ + "in your min API level OR use lifecycle:compiler annotation processor.", e);
+ }
+ }
+
CallbackInfo getInfo(Class klass) {
CallbackInfo existing = mCallbackMap.get(klass);
if (existing != null) {
@@ -106,7 +118,7 @@ class ClassesInfoCache {
}
}
- Method[] methods = declaredMethods != null ? declaredMethods : klass.getDeclaredMethods();
+ Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass);
boolean hasLifecycleMethods = false;
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index f1352446..1ddcb1a9 100644
--- a/android/arch/lifecycle/ComputableLiveData.java
+++ b/android/arch/lifecycle/ComputableLiveData.java
@@ -1,9 +1,136 @@
-//ComputableLiveData interface for tests
+/*
+ * 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.arch.lifecycle.LiveData;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A LiveData class that can be invalidated & computed on demand.
+ * <p>
+ * This is an internal class for now, might be public if we see the necessity.
+ *
+ * @param <T> The type of the live data
+ * @hide internal
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract class ComputableLiveData<T> {
- public ComputableLiveData(){}
- abstract protected T compute();
- public LiveData<T> getLiveData() {return null;}
- public void invalidate() {}
+
+ private final LiveData<T> mLiveData;
+
+ private AtomicBoolean mInvalid = new AtomicBoolean(true);
+ private AtomicBoolean mComputing = new AtomicBoolean(false);
+
+ /**
+ * Creates a computable live data which is computed when there are active observers.
+ * <p>
+ * It can also be invalidated via {@link #invalidate()} which will result in a call to
+ * {@link #compute()} if there are active observers (or when they start observing)
+ */
+ @SuppressWarnings("WeakerAccess")
+ public ComputableLiveData() {
+ mLiveData = new LiveData<T>() {
+ @Override
+ protected void onActive() {
+ // TODO if we make this class public, we should accept an executor
+ ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+ }
+ };
+ }
+
+ /**
+ * Returns the LiveData managed by this class.
+ *
+ * @return A LiveData that is controlled by ComputableLiveData.
+ */
+ @SuppressWarnings("WeakerAccess")
+ @NonNull
+ public LiveData<T> getLiveData() {
+ return mLiveData;
+ }
+
+ @VisibleForTesting
+ final Runnable mRefreshRunnable = new Runnable() {
+ @WorkerThread
+ @Override
+ public void run() {
+ boolean computed;
+ do {
+ computed = false;
+ // compute can happen only in 1 thread but no reason to lock others.
+ if (mComputing.compareAndSet(false, true)) {
+ // as long as it is invalid, keep computing.
+ try {
+ T value = null;
+ while (mInvalid.compareAndSet(true, false)) {
+ computed = true;
+ value = compute();
+ }
+ if (computed) {
+ mLiveData.postValue(value);
+ }
+ } finally {
+ // release compute lock
+ mComputing.set(false);
+ }
+ }
+ // check invalid after releasing compute lock to avoid the following scenario.
+ // Thread A runs compute()
+ // Thread A checks invalid, it is false
+ // Main thread sets invalid to true
+ // Thread B runs, fails to acquire compute lock and skips
+ // Thread A releases compute lock
+ // We've left invalid in set state. The check below recovers.
+ } while (computed && mInvalid.get());
+ }
+ };
+
+ // invalidation check always happens on the main thread
+ @VisibleForTesting
+ final Runnable mInvalidationRunnable = new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ boolean isActive = mLiveData.hasActiveObservers();
+ if (mInvalid.compareAndSet(false, true)) {
+ if (isActive) {
+ // TODO if we make this class public, we should accept an executor.
+ ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+ }
+ }
+ }
+ };
+
+ /**
+ * Invalidates the LiveData.
+ * <p>
+ * When there are active observers, this will trigger a call to {@link #compute()}.
+ */
+ public void invalidate() {
+ ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @WorkerThread
+ protected abstract T compute();
}
diff --git a/android/arch/lifecycle/DispatcherActivityCallbackTest.java b/android/arch/lifecycle/DispatcherActivityCallbackTest.java
deleted file mode 100644
index 86b25b60..00000000
--- a/android/arch/lifecycle/DispatcherActivityCallbackTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentTransaction;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class DispatcherActivityCallbackTest {
- @Test
- public void onCreateFrameworkActivity() {
- LifecycleDispatcher.DispatcherActivityCallback callback =
- new LifecycleDispatcher.DispatcherActivityCallback();
- Activity activity = mock(Activity.class);
- checkReportFragment(callback, activity);
- }
-
- @Test
- public void onCreateFragmentActivity() {
- LifecycleDispatcher.DispatcherActivityCallback callback =
- new LifecycleDispatcher.DispatcherActivityCallback();
- FragmentActivity activity = mock(FragmentActivity.class);
- FragmentManager fragmentManager = mock(FragmentManager.class);
- when(activity.getSupportFragmentManager()).thenReturn(fragmentManager);
-
- checkReportFragment(callback, activity);
-
- verify(activity).getSupportFragmentManager();
- verify(fragmentManager).registerFragmentLifecycleCallbacks(
- any(FragmentManager.FragmentLifecycleCallbacks.class), eq(true));
- }
-
- @SuppressLint("CommitTransaction")
- private void checkReportFragment(LifecycleDispatcher.DispatcherActivityCallback callback,
- Activity activity) {
- android.app.FragmentManager fm = mock(android.app.FragmentManager.class);
- FragmentTransaction transaction = mock(FragmentTransaction.class);
- when(activity.getFragmentManager()).thenReturn(fm);
- when(fm.beginTransaction()).thenReturn(transaction);
- when(transaction.add(any(Fragment.class), anyString())).thenReturn(transaction);
- callback.onActivityCreated(activity, mock(Bundle.class));
- verify(activity).getFragmentManager();
- verify(fm).beginTransaction();
- verify(transaction).add(any(ReportFragment.class), anyString());
- verify(transaction).commit();
- }
-}
diff --git a/android/arch/lifecycle/Lifecycle.java b/android/arch/lifecycle/Lifecycle.java
index 02db5ff9..c0a2090c 100644
--- a/android/arch/lifecycle/Lifecycle.java
+++ b/android/arch/lifecycle/Lifecycle.java
@@ -17,6 +17,7 @@
package android.arch.lifecycle;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
/**
* Defines an object that has an Android Lifecycle. {@link android.support.v4.app.Fragment Fragment}
@@ -83,7 +84,7 @@ public abstract class Lifecycle {
* @param observer The observer to notify.
*/
@MainThread
- public abstract void addObserver(LifecycleObserver observer);
+ public abstract void addObserver(@NonNull LifecycleObserver observer);
/**
* Removes the given observer from the observers list.
@@ -99,7 +100,7 @@ public abstract class Lifecycle {
* @param observer The observer to be removed.
*/
@MainThread
- public abstract void removeObserver(LifecycleObserver observer);
+ public abstract void removeObserver(@NonNull LifecycleObserver observer);
/**
* Returns the current state of the Lifecycle.
@@ -193,7 +194,7 @@ public abstract class Lifecycle {
* @param state State to compare with
* @return true if this State is greater or equal to the given {@code state}
*/
- public boolean isAtLeast(State state) {
+ public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}
diff --git a/android/arch/lifecycle/LifecycleDispatcher.java b/android/arch/lifecycle/LifecycleDispatcher.java
deleted file mode 100644
index 9fdec959..00000000
--- a/android/arch/lifecycle/LifecycleDispatcher.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.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 android.app.Activity;
-import android.app.Application;
-import android.arch.lifecycle.Lifecycle.State;
-import android.content.Context;
-import android.os.Bundle;
-import android.support.annotation.VisibleForTesting;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-
-import java.util.Collection;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * When initialized, it hooks into the Activity callback of the Application and observes
- * Activities. It is responsible to hook in child-fragments to activities and fragments to report
- * their lifecycle events. Another responsibility of this class is to mark as stopped all lifecycle
- * providers related to an activity as soon it is not safe to run a fragment transaction in this
- * activity.
- */
-class LifecycleDispatcher {
-
- private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
- + ".LifecycleDispatcher.report_fragment_tag";
-
- private static AtomicBoolean sInitialized = new AtomicBoolean(false);
-
- static void init(Context context) {
- if (sInitialized.getAndSet(true)) {
- return;
- }
- ((Application) context.getApplicationContext())
- .registerActivityLifecycleCallbacks(new DispatcherActivityCallback());
- }
-
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- static class DispatcherActivityCallback extends EmptyActivityLifecycleCallbacks {
- private final FragmentCallback mFragmentCallback;
-
- DispatcherActivityCallback() {
- mFragmentCallback = new FragmentCallback();
- }
-
- @Override
- public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
- if (activity instanceof FragmentActivity) {
- ((FragmentActivity) activity).getSupportFragmentManager()
- .registerFragmentLifecycleCallbacks(mFragmentCallback, true);
- }
- ReportFragment.injectIfNeededIn(activity);
- }
-
- @Override
- public void onActivityStopped(Activity activity) {
- if (activity instanceof FragmentActivity) {
- markState((FragmentActivity) activity, CREATED);
- }
- }
-
- @Override
- public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
- if (activity instanceof FragmentActivity) {
- markState((FragmentActivity) activity, CREATED);
- }
- }
- }
-
- @SuppressWarnings("WeakerAccess")
- public static class DestructionReportFragment extends Fragment {
- @Override
- public void onPause() {
- super.onPause();
- dispatch(ON_PAUSE);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- dispatch(ON_STOP);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- dispatch(ON_DESTROY);
- }
-
- protected void dispatch(Lifecycle.Event event) {
- dispatchIfLifecycleOwner(getParentFragment(), event);
- }
- }
-
- private static void markState(FragmentManager manager, State state) {
- Collection<Fragment> fragments = manager.getFragments();
- if (fragments == null) {
- return;
- }
- for (Fragment fragment : fragments) {
- if (fragment == null) {
- continue;
- }
- markStateIn(fragment, state);
- if (fragment.isAdded()) {
- markState(fragment.getChildFragmentManager(), state);
- }
- }
- }
-
- private static void markStateIn(Object object, State state) {
- if (object instanceof LifecycleRegistryOwner) {
- LifecycleRegistry registry = ((LifecycleRegistryOwner) object).getLifecycle();
- registry.markState(state);
- }
- }
-
- private static void markState(FragmentActivity activity, State state) {
- markStateIn(activity, state);
- markState(activity.getSupportFragmentManager(), state);
- }
-
- private static void dispatchIfLifecycleOwner(Fragment fragment, Lifecycle.Event event) {
- if (fragment instanceof LifecycleRegistryOwner) {
- ((LifecycleRegistryOwner) fragment).getLifecycle().handleLifecycleEvent(event);
- }
- }
-
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- static class FragmentCallback extends FragmentManager.FragmentLifecycleCallbacks {
-
- @Override
- public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {
- dispatchIfLifecycleOwner(f, ON_CREATE);
-
- if (!(f instanceof LifecycleRegistryOwner)) {
- return;
- }
-
- if (f.getChildFragmentManager().findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
- f.getChildFragmentManager().beginTransaction().add(new DestructionReportFragment(),
- REPORT_FRAGMENT_TAG).commit();
- }
- }
-
- @Override
- public void onFragmentStarted(FragmentManager fm, Fragment f) {
- dispatchIfLifecycleOwner(f, ON_START);
- }
-
- @Override
- public void onFragmentResumed(FragmentManager fm, Fragment f) {
- dispatchIfLifecycleOwner(f, ON_RESUME);
- }
- }
-}
diff --git a/android/arch/lifecycle/LifecycleOwner.java b/android/arch/lifecycle/LifecycleOwner.java
index 934cf3a2..068bac1b 100644
--- a/android/arch/lifecycle/LifecycleOwner.java
+++ b/android/arch/lifecycle/LifecycleOwner.java
@@ -16,6 +16,8 @@
package android.arch.lifecycle;
+import android.support.annotation.NonNull;
+
/**
* A class that has an Android lifecycle. These events can be used by custom components to
* handle lifecycle changes without implementing any code inside the Activity or the Fragment.
@@ -29,5 +31,6 @@ public interface LifecycleOwner {
*
* @return The lifecycle of the provider.
*/
+ @NonNull
Lifecycle getLifecycle();
}
diff --git a/android/arch/lifecycle/LifecycleRegistry.java b/android/arch/lifecycle/LifecycleRegistry.java
index b83e6b8a..bf8aff79 100644
--- a/android/arch/lifecycle/LifecycleRegistry.java
+++ b/android/arch/lifecycle/LifecycleRegistry.java
@@ -29,9 +29,12 @@ import static android.arch.lifecycle.Lifecycle.State.RESUMED;
import static android.arch.lifecycle.Lifecycle.State.STARTED;
import android.arch.core.internal.FastSafeIterableMap;
+import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.util.Log;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map.Entry;
@@ -44,6 +47,8 @@ import java.util.Map.Entry;
*/
public class LifecycleRegistry extends Lifecycle {
+ private static final String LOG_TAG = "LifecycleRegistry";
+
/**
* Custom list that keeps observers and can handle removals / additions during traversal.
*
@@ -59,8 +64,12 @@ public class LifecycleRegistry extends Lifecycle {
private State mState;
/**
* The provider that owns this Lifecycle.
+ * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak
+ * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
+ * because it keeps strong references on all other listeners, so you'll leak all of them as
+ * well.
*/
- private final LifecycleOwner mLifecycleOwner;
+ private final WeakReference<LifecycleOwner> mLifecycleOwner;
private int mAddingObserverCounter = 0;
@@ -86,19 +95,19 @@ public class LifecycleRegistry extends Lifecycle {
* @param provider The owner LifecycleOwner
*/
public LifecycleRegistry(@NonNull LifecycleOwner provider) {
- mLifecycleOwner = provider;
+ mLifecycleOwner = new WeakReference<>(provider);
mState = INITIALIZED;
}
/**
- * Only marks the current state as the given value. It doesn't dispatch any event to its
- * listeners.
+ * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
*
* @param state new state
*/
@SuppressWarnings("WeakerAccess")
- public void markState(State state) {
- mState = state;
+ @MainThread
+ public void markState(@NonNull State state) {
+ moveToState(state);
}
/**
@@ -109,8 +118,16 @@ public class LifecycleRegistry extends Lifecycle {
*
* @param event The event that was received
*/
- public void handleLifecycleEvent(Lifecycle.Event event) {
- mState = getStateAfter(event);
+ public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
+ State next = getStateAfter(event);
+ moveToState(next);
+ }
+
+ private void moveToState(State next) {
+ if (mState == next) {
+ return;
+ }
+ mState = next;
if (mHandlingEvent || mAddingObserverCounter != 0) {
mNewEventOccurred = true;
// we will figure out what to do on upper level.
@@ -140,7 +157,7 @@ public class LifecycleRegistry extends Lifecycle {
}
@Override
- public void addObserver(LifecycleObserver observer) {
+ public void addObserver(@NonNull LifecycleObserver observer) {
State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
@@ -148,15 +165,19 @@ public class LifecycleRegistry extends Lifecycle {
if (previous != null) {
return;
}
+ LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
+ if (lifecycleOwner == null) {
+ // it is null we should be destroyed. Fallback quickly
+ return;
+ }
boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
-
State targetState = calculateTargetState(observer);
mAddingObserverCounter++;
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
pushParentState(statefulObserver.mState);
- statefulObserver.dispatchEvent(mLifecycleOwner, upEvent(statefulObserver.mState));
+ statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / subling may have been changed recalculate
targetState = calculateTargetState(observer);
@@ -178,7 +199,7 @@ public class LifecycleRegistry extends Lifecycle {
}
@Override
- public void removeObserver(LifecycleObserver observer) {
+ public void removeObserver(@NonNull LifecycleObserver observer) {
// we consciously decided not to send destruction events here in opposition to addObserver.
// Our reasons for that:
// 1. These events haven't yet happened at all. In contrast to events in addObservers, that
@@ -258,7 +279,7 @@ public class LifecycleRegistry extends Lifecycle {
throw new IllegalArgumentException("Unexpected state value " + state);
}
- private void forwardPass() {
+ private void forwardPass(LifecycleOwner lifecycleOwner) {
Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator =
mObserverMap.iteratorWithAdditions();
while (ascendingIterator.hasNext() && !mNewEventOccurred) {
@@ -267,13 +288,13 @@ public class LifecycleRegistry extends Lifecycle {
while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred
&& mObserverMap.contains(entry.getKey()))) {
pushParentState(observer.mState);
- observer.dispatchEvent(mLifecycleOwner, upEvent(observer.mState));
+ observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState));
popParentState();
}
}
}
- private void backwardPass() {
+ private void backwardPass(LifecycleOwner lifecycleOwner) {
Iterator<Entry<LifecycleObserver, ObserverWithState>> descendingIterator =
mObserverMap.descendingIterator();
while (descendingIterator.hasNext() && !mNewEventOccurred) {
@@ -283,7 +304,7 @@ public class LifecycleRegistry extends Lifecycle {
&& mObserverMap.contains(entry.getKey()))) {
Event event = downEvent(observer.mState);
pushParentState(getStateAfter(event));
- observer.dispatchEvent(mLifecycleOwner, event);
+ observer.dispatchEvent(lifecycleOwner, event);
popParentState();
}
}
@@ -292,16 +313,22 @@ public class LifecycleRegistry extends Lifecycle {
// happens only on the top of stack (never in reentrance),
// so it doesn't have to take in account parents
private void sync() {
+ LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
+ if (lifecycleOwner == null) {
+ Log.w(LOG_TAG, "LifecycleOwner is garbage collected, you shouldn't try dispatch "
+ + "new events from it.");
+ return;
+ }
while (!isSynced()) {
mNewEventOccurred = false;
// no need to check eldest for nullability, because isSynced does it for us.
if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
- backwardPass();
+ backwardPass(lifecycleOwner);
}
Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
if (!mNewEventOccurred && newest != null
&& mState.compareTo(newest.getValue().mState) > 0) {
- forwardPass();
+ forwardPass(lifecycleOwner);
}
}
mNewEventOccurred = false;
diff --git a/android/arch/lifecycle/LifecycleRegistryOwner.java b/android/arch/lifecycle/LifecycleRegistryOwner.java
index 38eeb6d3..0c67fefe 100644
--- a/android/arch/lifecycle/LifecycleRegistryOwner.java
+++ b/android/arch/lifecycle/LifecycleRegistryOwner.java
@@ -16,6 +16,8 @@
package android.arch.lifecycle;
+import android.support.annotation.NonNull;
+
/**
* @deprecated Use {@code android.support.v7.app.AppCompatActivity}
* which extends {@link LifecycleOwner}, so there are no use cases for this class.
@@ -23,6 +25,7 @@ package android.arch.lifecycle;
@SuppressWarnings({"WeakerAccess", "unused"})
@Deprecated
public interface LifecycleRegistryOwner extends LifecycleOwner {
+ @NonNull
@Override
LifecycleRegistry getLifecycle();
}
diff --git a/android/arch/lifecycle/LifecycleRegistryTest.java b/android/arch/lifecycle/LifecycleRegistryTest.java
index 6506454d..2a7bbad2 100644
--- a/android/arch/lifecycle/LifecycleRegistryTest.java
+++ b/android/arch/lifecycle/LifecycleRegistryTest.java
@@ -566,6 +566,25 @@ public class LifecycleRegistryTest {
verify(observer).onCreate();
}
+ private static void forceGc() {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ }
+
+ @Test
+ public void goneLifecycleOwner() {
+ fullyInitializeRegistry();
+ mLifecycleOwner = null;
+ forceGc();
+ TestObserver observer = mock(TestObserver.class);
+ mRegistry.addObserver(observer);
+ verify(observer, never()).onCreate();
+ verify(observer, never()).onStart();
+ verify(observer, never()).onResume();
+ }
+
private void dispatchEvent(Lifecycle.Event event) {
when(mLifecycle.getCurrentState()).thenReturn(LifecycleRegistry.getStateAfter(event));
mRegistry.handleLifecycleEvent(event);
diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java
index 3aea6acb..5b09c32f 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,4 +1,410 @@
-//LiveData interface for tests
+/*
+ * 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;
-public class LiveData<T> {
+
+import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.core.internal.SafeIterableMap;
+import android.arch.lifecycle.Lifecycle.State;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * LiveData is a data holder class that can be observed within a given lifecycle.
+ * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
+ * this observer will be notified about modifications of the wrapped data only if the paired
+ * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
+ * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
+ * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
+ * about modifications. For those observers, you should manually call
+ * {@link #removeObserver(Observer)}.
+ *
+ * <p> An observer added with a Lifecycle will be automatically removed if the corresponding
+ * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
+ * activities and fragments where they can safely observe LiveData and not worry about leaks:
+ * they will be instantly unsubscribed when they are destroyed.
+ *
+ * <p>
+ * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
+ * to get notified when number of active {@link Observer}s change between 0 and 1.
+ * This allows LiveData to release any heavy resources when it does not have any Observers that
+ * are actively observing.
+ * <p>
+ * This class is designed to hold individual data fields of {@link ViewModel},
+ * but can also be used for sharing data between different modules in your application
+ * in a decoupled fashion.
+ *
+ * @param <T> The type of data held by this instance
+ * @see ViewModel
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+// TODO: Thread checks are too strict right now, we may consider automatically moving them to main
+// thread.
+public abstract class LiveData<T> {
+ private final Object mDataLock = new Object();
+ static final int START_VERSION = -1;
+ private static final Object NOT_SET = new Object();
+
+ private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() {
+
+ private LifecycleRegistry mRegistry = init();
+
+ private LifecycleRegistry init() {
+ LifecycleRegistry registry = new LifecycleRegistry(this);
+ registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+ registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+ return registry;
+ }
+
+ @Override
+ public Lifecycle getLifecycle() {
+ return mRegistry;
+ }
+ };
+
+ private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers =
+ new SafeIterableMap<>();
+
+ // how many observers are in active state
+ private int mActiveCount = 0;
+ private volatile Object mData = NOT_SET;
+ // when setData is called, we set the pending data and actual data swap happens on the main
+ // thread
+ private volatile Object mPendingData = NOT_SET;
+ private int mVersion = START_VERSION;
+
+ private boolean mDispatchingValue;
+ @SuppressWarnings("FieldCanBeLocal")
+ private boolean mDispatchInvalidated;
+ private final Runnable mPostValueRunnable = new Runnable() {
+ @Override
+ public void run() {
+ Object newValue;
+ synchronized (mDataLock) {
+ newValue = mPendingData;
+ mPendingData = NOT_SET;
+ }
+ //noinspection unchecked
+ setValue((T) newValue);
+ }
+ };
+
+ private void considerNotify(LifecycleBoundObserver observer) {
+ if (!observer.active) {
+ return;
+ }
+ // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
+ //
+ // we still first check observer.active to keep it as the entrance for events. So even if
+ // the observer moved to an active state, if we've not received that event, we better not
+ // notify for a more predictable notification order.
+ if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
+ observer.activeStateChanged(false);
+ return;
+ }
+ if (observer.lastVersion >= mVersion) {
+ return;
+ }
+ observer.lastVersion = mVersion;
+ //noinspection unchecked
+ observer.observer.onChanged((T) mData);
+ }
+
+ private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
+ if (mDispatchingValue) {
+ mDispatchInvalidated = true;
+ return;
+ }
+ mDispatchingValue = true;
+ do {
+ mDispatchInvalidated = false;
+ if (initiator != null) {
+ considerNotify(initiator);
+ initiator = null;
+ } else {
+ for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
+ mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
+ considerNotify(iterator.next().getValue());
+ if (mDispatchInvalidated) {
+ break;
+ }
+ }
+ }
+ } while (mDispatchInvalidated);
+ mDispatchingValue = false;
+ }
+
+ /**
+ * Adds the given observer to the observers list within the lifespan of the given
+ * owner. The events are dispatched on the main thread. If LiveData already has data
+ * set, it will be delivered to the observer.
+ * <p>
+ * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
+ * or {@link Lifecycle.State#RESUMED} state (active).
+ * <p>
+ * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
+ * automatically be removed.
+ * <p>
+ * When data changes while the {@code owner} is not active, it will not receive any updates.
+ * If it becomes active again, it will receive the last available data automatically.
+ * <p>
+ * LiveData keeps a strong reference to the observer and the owner as long as the
+ * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
+ * the observer &amp; the owner.
+ * <p>
+ * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
+ * ignores the call.
+ * <p>
+ * If the given owner, observer tuple is already in the list, the call is ignored.
+ * If the observer is already in the list with another owner, LiveData throws an
+ * {@link IllegalArgumentException}.
+ *
+ * @param owner The LifecycleOwner which controls the observer
+ * @param observer The observer that will receive the events
+ */
+ @MainThread
+ public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
+ if (owner.getLifecycle().getCurrentState() == DESTROYED) {
+ // ignore
+ return;
+ }
+ LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
+ LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
+ if (existing != null && existing.owner != wrapper.owner) {
+ throw new IllegalArgumentException("Cannot add the same observer"
+ + " with different lifecycles");
+ }
+ if (existing != null) {
+ return;
+ }
+ owner.getLifecycle().addObserver(wrapper);
+ }
+
+ /**
+ * Adds the given observer to the observers list. This call is similar to
+ * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
+ * is always active. This means that the given observer will receive all events and will never
+ * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
+ * observing this LiveData.
+ * While LiveData has one of such observers, it will be considered
+ * as active.
+ * <p>
+ * If the observer was already added with an owner to this LiveData, LiveData throws an
+ * {@link IllegalArgumentException}.
+ *
+ * @param observer The observer that will receive the events
+ */
+ @MainThread
+ public void observeForever(@NonNull Observer<T> observer) {
+ observe(ALWAYS_ON, observer);
+ }
+
+ /**
+ * Removes the given observer from the observers list.
+ *
+ * @param observer The Observer to receive events.
+ */
+ @MainThread
+ public void removeObserver(@NonNull final Observer<T> observer) {
+ assertMainThread("removeObserver");
+ LifecycleBoundObserver removed = mObservers.remove(observer);
+ if (removed == null) {
+ return;
+ }
+ removed.owner.getLifecycle().removeObserver(removed);
+ removed.activeStateChanged(false);
+ }
+
+ /**
+ * Removes all observers that are tied to the given {@link LifecycleOwner}.
+ *
+ * @param owner The {@code LifecycleOwner} scope for the observers to be removed.
+ */
+ @MainThread
+ public void removeObservers(@NonNull final LifecycleOwner owner) {
+ assertMainThread("removeObservers");
+ for (Map.Entry<Observer<T>, LifecycleBoundObserver> entry : mObservers) {
+ if (entry.getValue().owner == owner) {
+ removeObserver(entry.getKey());
+ }
+ }
+ }
+
+ /**
+ * Posts a task to a main thread to set the given value. So if you have a following code
+ * executed in the main thread:
+ * <pre class="prettyprint">
+ * liveData.postValue("a");
+ * liveData.setValue("b");
+ * </pre>
+ * The value "b" would be set at first and later the main thread would override it with
+ * the value "a".
+ * <p>
+ * If you called this method multiple times before a main thread executed a posted task, only
+ * the last value would be dispatched.
+ *
+ * @param value The new value
+ */
+ protected void postValue(T value) {
+ boolean postTask;
+ synchronized (mDataLock) {
+ postTask = mPendingData == NOT_SET;
+ mPendingData = value;
+ }
+ if (!postTask) {
+ return;
+ }
+ ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
+ }
+
+ /**
+ * Sets the value. If there are active observers, the value will be dispatched to them.
+ * <p>
+ * This method must be called from the main thread. If you need set a value from a background
+ * thread, you can use {@link #postValue(Object)}
+ *
+ * @param value The new value
+ */
+ @MainThread
+ protected void setValue(T value) {
+ assertMainThread("setValue");
+ mVersion++;
+ mData = value;
+ dispatchingValue(null);
+ }
+
+ /**
+ * Returns the current value.
+ * Note that calling this method on a background thread does not guarantee that the latest
+ * value set will be received.
+ *
+ * @return the current value
+ */
+ @Nullable
+ public T getValue() {
+ Object data = mData;
+ if (data != NOT_SET) {
+ //noinspection unchecked
+ return (T) data;
+ }
+ return null;
+ }
+
+ int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Called when the number of active observers change to 1 from 0.
+ * <p>
+ * This callback can be used to know that this LiveData is being used thus should be kept
+ * up to date.
+ */
+ protected void onActive() {
+
+ }
+
+ /**
+ * Called when the number of active observers change from 1 to 0.
+ * <p>
+ * This does not mean that there are no observers left, there may still be observers but their
+ * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
+ * (like an Activity in the back stack).
+ * <p>
+ * You can check if there are observers via {@link #hasObservers()}.
+ */
+ protected void onInactive() {
+
+ }
+
+ /**
+ * Returns true if this LiveData has observers.
+ *
+ * @return true if this LiveData has observers
+ */
+ public boolean hasObservers() {
+ return mObservers.size() > 0;
+ }
+
+ /**
+ * Returns true if this LiveData has active observers.
+ *
+ * @return true if this LiveData has active observers
+ */
+ public boolean hasActiveObservers() {
+ return mActiveCount > 0;
+ }
+
+ class LifecycleBoundObserver implements GenericLifecycleObserver {
+ public final LifecycleOwner owner;
+ public final Observer<T> observer;
+ public boolean active;
+ public int lastVersion = START_VERSION;
+
+ LifecycleBoundObserver(LifecycleOwner owner, Observer<T> observer) {
+ this.owner = owner;
+ this.observer = observer;
+ }
+
+ @Override
+ public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+ if (owner.getLifecycle().getCurrentState() == DESTROYED) {
+ removeObserver(observer);
+ return;
+ }
+ // immediately set active state, so we'd never dispatch anything to inactive
+ // owner
+ activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
+ }
+
+ void activeStateChanged(boolean newActive) {
+ if (newActive == active) {
+ return;
+ }
+ active = newActive;
+ boolean wasInactive = LiveData.this.mActiveCount == 0;
+ LiveData.this.mActiveCount += active ? 1 : -1;
+ if (wasInactive && active) {
+ onActive();
+ }
+ if (LiveData.this.mActiveCount == 0 && !active) {
+ onInactive();
+ }
+ if (active) {
+ dispatchingValue(this);
+ }
+ }
+ }
+
+ static boolean isActiveState(State state) {
+ return state.isAtLeast(STARTED);
+ }
+
+ private void assertMainThread(String methodName) {
+ if (!ArchTaskExecutor.getInstance().isMainThread()) {
+ throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
+ + " thread");
+ }
+ }
}
diff --git a/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java b/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
new file mode 100644
index 00000000..836cfff0
--- /dev/null
+++ b/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.app.Instrumentation;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
+import android.arch.lifecycle.testapp.CollectingSupportFragment;
+import android.arch.lifecycle.testapp.NavigationDialogActivity;
+import android.content.Intent;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+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.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LiveDataOnSaveInstanceStateTest {
+ @Rule
+ public ActivityTestRule<CollectingSupportActivity> mActivityTestRule =
+ new ActivityTestRule<>(CollectingSupportActivity.class);
+
+ @Test
+ @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+ public void liveData_partiallyObscuredActivity_maxSdkM() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+
+ liveData_partiallyObscuredLifecycleOwner_maxSdkM(activity);
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+ public void liveData_partiallyObscuredActivityWithFragment_maxSdkM() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
+
+ liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment);
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+ public void liveData_partiallyObscuredActivityFragmentInFragment_maxSdkM() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ CollectingSupportFragment fragment2 = new CollectingSupportFragment();
+ mActivityTestRule.runOnUiThread(() -> {
+ activity.replaceFragment(fragment);
+ fragment.replaceFragment(fragment2);
+ });
+
+ liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment2);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+ public void liveData_partiallyObscuredActivity_minSdkN() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+
+ liveData_partiallyObscuredLifecycleOwner_minSdkN(activity);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+ public void liveData_partiallyObscuredActivityWithFragment_minSdkN() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
+
+ liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+ public void liveData_partiallyObscuredActivityFragmentInFragment_minSdkN() throws Throwable {
+ CollectingSupportActivity activity = mActivityTestRule.getActivity();
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ CollectingSupportFragment fragment2 = new CollectingSupportFragment();
+ mActivityTestRule.runOnUiThread(() -> {
+ activity.replaceFragment(fragment);
+ fragment.replaceFragment(fragment2);
+ });
+
+ liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment2);
+ }
+
+ private void liveData_partiallyObscuredLifecycleOwner_maxSdkM(LifecycleOwner lifecycleOwner)
+ throws Throwable {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
+ mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
+
+ TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+
+ mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
+
+ final FragmentActivity dialogActivity = launchDialog();
+
+ TestUtils.waitTillCreated(lifecycleOwner, mActivityTestRule);
+
+ // Change the LiveData value and assert that the observer is not called given that the
+ // lifecycle is in the CREATED state.
+ mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
+ assertThat(atomicInteger.get(), is(0));
+
+ // Finish the dialog Activity, wait for the main activity to be resumed, and assert that
+ // the observer's onChanged method is called.
+ mActivityTestRule.runOnUiThread(dialogActivity::finish);
+ TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+ assertThat(atomicInteger.get(), is(1));
+ }
+
+ private void liveData_partiallyObscuredLifecycleOwner_minSdkN(LifecycleOwner lifecycleOwner)
+ throws Throwable {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
+ mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
+
+ TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+
+ mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
+
+ // Launch the NavigationDialogActivity, partially obscuring the activity, and wait for the
+ // lifecycleOwner to hit onPause (or enter the STARTED state). On API 24 and above, this
+ // onPause should be the last lifecycle method called (and the STARTED state should be the
+ // final resting state).
+ launchDialog();
+ TestUtils.waitTillStarted(lifecycleOwner, mActivityTestRule);
+
+ // Change the LiveData's value and verify that the observer's onChanged method is called
+ // since we are in the STARTED state.
+ mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
+ assertThat(atomicInteger.get(), is(1));
+ }
+
+ private FragmentActivity launchDialog() throws Throwable {
+ Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+ NavigationDialogActivity.class.getCanonicalName(), null, false);
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.addMonitor(monitor);
+
+ FragmentActivity activity = mActivityTestRule.getActivity();
+ // helps with less flaky API 16 tests
+ Intent intent = new Intent(activity, NavigationDialogActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ activity.startActivity(intent);
+ FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
+ TestUtils.waitTillResumed(fragmentActivity, mActivityTestRule);
+ return fragmentActivity;
+ }
+}
diff --git a/android/arch/lifecycle/LiveDataTest.java b/android/arch/lifecycle/LiveDataTest.java
index 9f0b4257..647d5d7a 100644
--- a/android/arch/lifecycle/LiveDataTest.java
+++ b/android/arch/lifecycle/LiveDataTest.java
@@ -53,18 +53,29 @@ import org.mockito.Mockito;
public class LiveDataTest {
private PublicLiveData<String> mLiveData;
private LifecycleOwner mOwner;
+ private LifecycleOwner mOwner2;
private LifecycleRegistry mRegistry;
+ private LifecycleRegistry mRegistry2;
private MethodExec mActiveObserversChanged;
private boolean mInObserver;
@Before
public void init() {
mLiveData = new PublicLiveData<>();
+
+ mActiveObserversChanged = mock(MethodExec.class);
+ mLiveData.activeObserversChanged = mActiveObserversChanged;
+
mOwner = mock(LifecycleOwner.class);
+
mRegistry = new LifecycleRegistry(mOwner);
when(mOwner.getLifecycle()).thenReturn(mRegistry);
- mActiveObserversChanged = mock(MethodExec.class);
- mLiveData.activeObserversChanged = mActiveObserversChanged;
+
+ mOwner2 = mock(LifecycleOwner.class);
+
+ mRegistry2 = new LifecycleRegistry(mOwner2);
+ when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
+
mInObserver = false;
}
@@ -159,14 +170,11 @@ public class LiveDataTest {
@Test
public void testAddSameObserverIn2LifecycleOwners() {
Observer<String> observer = (Observer<String>) mock(Observer.class);
- LifecycleOwner owner2 = mock(LifecycleOwner.class);
- LifecycleRegistry registry2 = new LifecycleRegistry(owner2);
- when(owner2.getLifecycle()).thenReturn(registry2);
mLiveData.observe(mOwner, observer);
Throwable throwable = null;
try {
- mLiveData.observe(owner2, observer);
+ mLiveData.observe(mOwner2, observer);
} catch (Throwable t) {
throwable = t;
}
@@ -456,6 +464,210 @@ public class LiveDataTest {
inOrder.verifyNoMoreInteractions();
}
+ @Test
+ public void setValue_neverActive_observerOnChangedNotCalled() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, observer);
+
+ mLiveData.setValue("1");
+
+ verify(observer, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void setValue_twoObserversTwoStartedOwners_onChangedCalledOnBoth() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ mLiveData.setValue("1");
+
+ verify(observer1).onChanged("1");
+ verify(observer2).onChanged("1");
+ }
+
+ @Test
+ public void setValue_twoObserversOneStartedOwner_onChangedCalledOnOneCorrectObserver() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ mLiveData.setValue("1");
+
+ verify(observer1).onChanged("1");
+ verify(observer2, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void setValue_twoObserversBothStartedAfterSetValue_onChangedCalledOnBoth() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mLiveData.setValue("1");
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(observer1).onChanged("1");
+ verify(observer1).onChanged("1");
+ }
+
+ @Test
+ public void setValue_twoObserversOneStartedAfterSetValue_onChangedCalledOnCorrectObserver() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mLiveData.setValue("1");
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(observer1).onChanged("1");
+ verify(observer2, never()).onChanged(anyString());
+ }
+
+ @Test
+ public void setValue_twoObserversOneStarted_liveDataBecomesActive() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(mActiveObserversChanged).onCall(true);
+ }
+
+ @Test
+ public void setValue_twoObserversOneStopped_liveDataStaysActive() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ verify(mActiveObserversChanged).onCall(true);
+
+ reset(mActiveObserversChanged);
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ }
+
+ /**
+ * Verifies that if a lifecycle's state changes without an event, and changes to something that
+ * LiveData would become inactive in response to, LiveData will detect the change upon new data
+ * being set and become inactive. Also verifies that once the lifecycle enters into a state
+ * that LiveData should become active to, that it does indeed become active.
+ */
+ @Test
+ public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_oneObserver() {
+ Observer<String> observer = (Observer<String>) mock(Observer.class);
+ mLiveData.observe(mOwner, observer);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ // Marking state as CREATED should call onInactive.
+ reset(mActiveObserversChanged);
+ mRegistry.markState(Lifecycle.State.CREATED);
+ verify(mActiveObserversChanged).onCall(false);
+ reset(mActiveObserversChanged);
+
+ // Setting a new value should trigger LiveData to realize the Lifecycle it is observing
+ // is in a state where the LiveData should be inactive, so the LiveData will call onInactive
+ // and the Observer shouldn't be affected.
+ mLiveData.setValue("1");
+
+ // state is already CREATED so should not call again
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ verify(observer, never()).onChanged(anyString());
+
+ // Sanity check. Because we've only marked the state as CREATED, sending ON_START
+ // should re-dispatch events.
+ reset(mActiveObserversChanged);
+ reset(observer);
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ verify(mActiveObserversChanged).onCall(true);
+ verify(observer).onChanged("1");
+ }
+
+ /**
+ * This test verifies that LiveData will detect changes in LifecycleState that would make it
+ * inactive upon the setting of new data, but only if all of the Lifecycles it's observing
+ * are all in those states. It also makes sure that once it is inactive, that it will become
+ * active again once one of the lifecycles it's observing moves to an appropriate state.
+ */
+ @Test
+ public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_twoObservers() {
+ Observer<String> observer1 = mock(Observer.class);
+ Observer<String> observer2 = mock(Observer.class);
+
+ mLiveData.observe(mOwner, observer1);
+ mLiveData.observe(mOwner2, observer2);
+
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+ // Marking the state to created won't change LiveData to be inactive.
+ reset(mActiveObserversChanged);
+ mRegistry.markState(Lifecycle.State.CREATED);
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+
+ // After setting a value, the LiveData will stay active because there is still a STARTED
+ // lifecycle being observed. The one Observer associated with the STARTED lifecycle will
+ // also have been called, but the other Observer will not have been called.
+ reset(observer1);
+ reset(observer2);
+ mLiveData.setValue("1");
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ verify(observer1, never()).onChanged(anyString());
+ verify(observer2).onChanged("1");
+
+ // Now we set the other Lifecycle to be inactive, live data should become inactive.
+ reset(observer1);
+ reset(observer2);
+ mRegistry2.markState(Lifecycle.State.CREATED);
+ verify(mActiveObserversChanged).onCall(false);
+ verify(observer1, never()).onChanged(anyString());
+ verify(observer2, never()).onChanged(anyString());
+
+ // Now we post another value, because both lifecycles are in the Created state, live data
+ // will not dispatch any values
+ reset(mActiveObserversChanged);
+ mLiveData.setValue("2");
+ verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ verify(observer1, never()).onChanged(anyString());
+ verify(observer2, never()).onChanged(anyString());
+
+ // Now that the first Lifecycle has been moved back to the Resumed state, the LiveData will
+ // be made active and it's associated Observer will be called with the new value, but the
+ // Observer associated with the Lifecycle that is still in the Created state won't be
+ // called.
+ reset(mActiveObserversChanged);
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+ verify(mActiveObserversChanged).onCall(true);
+ verify(observer1).onChanged("2");
+ verify(observer2, never()).onChanged(anyString());
+ }
+
@SuppressWarnings("WeakerAccess")
static class PublicLiveData<T> extends LiveData<T> {
// cannot spy due to internal calls
diff --git a/android/arch/lifecycle/MediatorLiveData.java b/android/arch/lifecycle/MediatorLiveData.java
index 672b3a3b..58647394 100644
--- a/android/arch/lifecycle/MediatorLiveData.java
+++ b/android/arch/lifecycle/MediatorLiveData.java
@@ -19,16 +19,49 @@ package android.arch.lifecycle;
import android.arch.core.internal.SafeIterableMap;
import android.support.annotation.CallSuper;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Map;
/**
- * {@link LiveData} subclass which may observer other {@code LiveData} objects and react on
+ * {@link LiveData} subclass which may observe other {@code LiveData} objects and react on
* {@code OnChanged} events from them.
* <p>
* This class correctly propagates its active/inactive states down to source {@code LiveData}
* objects.
+ * <p>
+ * Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them
+ * {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object:
+ * {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for
+ * the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback
+ * is called for either of them, we set a new value in {@code liveDataMerger}.
+ *
+ * <pre>
+ * LiveData<Integer> liveData1 = ...;
+ * LiveData<Integer> liveData2 = ...;
+ *
+ * MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
+ * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
+ * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
+ * </pre>
+ * <p>
+ * Let's consider that we only want 10 values emitted by {@code liveData1}, to be
+ * merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code
+ * liveData1} and remove it as a source.
+ * <pre>
+ * liveDataMerger.addSource(liveData1, new Observer<Integer>() {
+ * private int count = 1;
+ *
+ * {@literal @}Override public void onChanged(@Nullable Integer s) {
+ * count++;
+ * liveDataMerger.setValue(s);
+ * if (count > 10) {
+ * liveDataMerger.removeSource(liveData1);
+ * }
+ * }
+ * });
+ * </pre>
*
* @param <T> The type of data hold by this instance
*/
@@ -49,7 +82,7 @@ public class MediatorLiveData<T> extends MutableLiveData<T> {
* @param <S> The type of data hold by {@code source} LiveData
*/
@MainThread
- public <S> void addSource(LiveData<S> source, Observer<S> onChanged) {
+ public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<S> onChanged) {
Source<S> e = new Source<>(source, onChanged);
Source<?> existing = mSources.putIfAbsent(source, e);
if (existing != null && existing.mObserver != onChanged) {
@@ -71,7 +104,7 @@ public class MediatorLiveData<T> extends MutableLiveData<T> {
* @param <S> the type of data hold by {@code source} LiveData
*/
@MainThread
- public <S> void removeSource(LiveData<S> toRemote) {
+ public <S> void removeSource(@NonNull LiveData<S> toRemote) {
Source<?> source = mSources.remove(toRemote);
if (source != null) {
source.unplug();
diff --git a/android/arch/lifecycle/MissingClassTest.java b/android/arch/lifecycle/MissingClassTest.java
new file mode 100644
index 00000000..81a07564
--- /dev/null
+++ b/android/arch/lifecycle/MissingClassTest.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.app.PictureInPictureParams;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
+@SmallTest
+public class MissingClassTest {
+ public static class ObserverWithMissingClasses {
+ @SuppressWarnings("unused")
+ public void newApiMethod(PictureInPictureParams params) {}
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ public void onResume() {}
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMissingApi() {
+ new ReflectiveGenericLifecycleObserver(new ObserverWithMissingClasses());
+ }
+}
diff --git a/android/arch/lifecycle/PartiallyCoveredActivityTest.java b/android/arch/lifecycle/PartiallyCoveredActivityTest.java
new file mode 100644
index 00000000..07a9dc5a
--- /dev/null
+++ b/android/arch/lifecycle/PartiallyCoveredActivityTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.START;
+import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP;
+import static android.arch.lifecycle.TestUtils.flatMap;
+import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+import android.app.Instrumentation;
+import android.arch.lifecycle.testapp.CollectingLifecycleOwner;
+import android.arch.lifecycle.testapp.CollectingSupportActivity;
+import android.arch.lifecycle.testapp.CollectingSupportFragment;
+import android.arch.lifecycle.testapp.NavigationDialogActivity;
+import android.arch.lifecycle.testapp.TestEvent;
+import android.content.Intent;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.Pair;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Runs tests about the state when an activity is partially covered by another activity. Pre
+ * API 24, framework behavior changes so the test rely on whether state is saved or not and makes
+ * assertions accordingly.
+ */
+@SuppressWarnings("unchecked")
+@RunWith(Parameterized.class)
+@LargeTest
+public class PartiallyCoveredActivityTest {
+ private static final List[] IF_SAVED = new List[]{
+ // when overlaid
+ flatMap(CREATE, START, RESUME, PAUSE,
+ singletonList(new Pair<>(LIFECYCLE_EVENT, ON_STOP))),
+ // post dialog dismiss
+ asList(new Pair<>(OWNER_CALLBACK, ON_RESUME),
+ new Pair<>(LIFECYCLE_EVENT, ON_START),
+ new Pair<>(LIFECYCLE_EVENT, ON_RESUME)),
+ // post finish
+ flatMap(PAUSE, STOP, DESTROY)};
+
+ private static final List[] IF_NOT_SAVED = new List[]{
+ // when overlaid
+ flatMap(CREATE, START, RESUME, PAUSE),
+ // post dialog dismiss
+ flatMap(RESUME),
+ // post finish
+ flatMap(PAUSE, STOP, DESTROY)};
+
+ private static final boolean sShouldSave = Build.VERSION.SDK_INT < Build.VERSION_CODES.N;
+ private static final List<Pair<TestEvent, Lifecycle.Event>>[] EXPECTED =
+ sShouldSave ? IF_SAVED : IF_NOT_SAVED;
+
+ @Rule
+ public ActivityTestRule<CollectingSupportActivity> activityRule =
+ new ActivityTestRule<CollectingSupportActivity>(
+ CollectingSupportActivity.class) {
+ @Override
+ protected Intent getActivityIntent() {
+ // helps with less flaky API 16 tests
+ Intent intent = new Intent(InstrumentationRegistry.getTargetContext(),
+ CollectingSupportActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ return intent;
+ }
+ };
+ private final boolean mDismissDialog;
+
+ @Parameterized.Parameters(name = "dismissDialog_{0}")
+ public static List<Boolean> dismissDialog() {
+ return asList(true, false);
+ }
+
+ public PartiallyCoveredActivityTest(boolean dismissDialog) {
+ mDismissDialog = dismissDialog;
+ }
+
+ @Test
+ public void coveredWithDialog_activity() throws Throwable {
+ final CollectingSupportActivity activity = activityRule.getActivity();
+ runTest(activity);
+ }
+
+ @Test
+ public void coveredWithDialog_fragment() throws Throwable {
+ CollectingSupportFragment fragment = new CollectingSupportFragment();
+ activityRule.runOnUiThread(() -> activityRule.getActivity().replaceFragment(fragment));
+ runTest(fragment);
+ }
+
+ @Test
+ public void coveredWithDialog_childFragment() throws Throwable {
+ CollectingSupportFragment parentFragment = new CollectingSupportFragment();
+ CollectingSupportFragment childFragment = new CollectingSupportFragment();
+ activityRule.runOnUiThread(() -> {
+ activityRule.getActivity().replaceFragment(parentFragment);
+ parentFragment.replaceFragment(childFragment);
+ });
+ runTest(childFragment);
+ }
+
+ private void runTest(CollectingLifecycleOwner owner) throws Throwable {
+ TestUtils.waitTillResumed(owner, activityRule);
+ FragmentActivity dialog = launchDialog();
+ assertStateSaving();
+ waitForIdle();
+ assertThat(owner.copyCollectedEvents(), is(EXPECTED[0]));
+ List<Pair<TestEvent, Lifecycle.Event>> expected;
+ if (mDismissDialog) {
+ dialog.finish();
+ TestUtils.waitTillResumed(activityRule.getActivity(), activityRule);
+ assertThat(owner.copyCollectedEvents(), is(flatMap(EXPECTED[0], EXPECTED[1])));
+ expected = flatMap(EXPECTED[0], EXPECTED[1], EXPECTED[2]);
+ } else {
+ expected = flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY);
+ }
+ CollectingSupportActivity activity = activityRule.getActivity();
+ activityRule.finishActivity();
+ TestUtils.waitTillDestroyed(activity, activityRule);
+ assertThat(owner.copyCollectedEvents(), is(expected));
+ }
+
+ // test sanity
+ private void assertStateSaving() throws ExecutionException, InterruptedException {
+ final CollectingSupportActivity activity = activityRule.getActivity();
+ if (sShouldSave) {
+ // state should be saved. wait for it to be saved
+ assertThat("test sanity",
+ activity.waitForStateSave(20), is(true));
+ assertThat("test sanity", activity.getSupportFragmentManager()
+ .isStateSaved(), is(true));
+ } else {
+ // should should not be saved
+ assertThat("test sanity", activity.getSupportFragmentManager()
+ .isStateSaved(), is(false));
+ }
+ }
+
+ private void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private FragmentActivity launchDialog() throws Throwable {
+ Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+ NavigationDialogActivity.class.getCanonicalName(), null, false);
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.addMonitor(monitor);
+
+ FragmentActivity activity = activityRule.getActivity();
+
+ Intent intent = new Intent(activity, NavigationDialogActivity.class);
+ // disabling animations helps with less flaky API 16 tests
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ activity.startActivity(intent);
+ FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
+ TestUtils.waitTillResumed(fragmentActivity, activityRule);
+ return fragmentActivity;
+ }
+}
diff --git a/android/arch/lifecycle/ProcessLifecycleOwner.java b/android/arch/lifecycle/ProcessLifecycleOwner.java
index e2a12563..179e2c47 100644
--- a/android/arch/lifecycle/ProcessLifecycleOwner.java
+++ b/android/arch/lifecycle/ProcessLifecycleOwner.java
@@ -22,6 +22,7 @@ import android.arch.lifecycle.ReportFragment.ActivityInitializationListener;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
+import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
/**
@@ -156,7 +157,8 @@ public class ProcessLifecycleOwner implements LifecycleOwner {
app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
- ReportFragment .get(activity).setProcessListener(mInitializationListener);
+ ReportFragment.injectIfNeededIn(activity);
+ ReportFragment.get(activity).setProcessListener(mInitializationListener);
}
@Override
@@ -171,6 +173,7 @@ public class ProcessLifecycleOwner implements LifecycleOwner {
});
}
+ @NonNull
@Override
public Lifecycle getLifecycle() {
return mRegistry;
diff --git a/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java b/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
index ac278c0c..8ba297fe 100644
--- a/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java
+++ b/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
@@ -29,10 +29,9 @@ import android.support.annotation.RestrictTo;
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class LifecycleRuntimeTrojanProvider extends ContentProvider {
+public class ProcessLifecycleOwnerInitializer extends ContentProvider {
@Override
public boolean onCreate() {
- LifecycleDispatcher.init(getContext());
ProcessLifecycleOwner.init(getContext());
return true;
}
diff --git a/android/arch/lifecycle/ProcessOwnerTest.java b/android/arch/lifecycle/ProcessOwnerTest.java
index 37bdcdb4..77baf94c 100644
--- a/android/arch/lifecycle/ProcessOwnerTest.java
+++ b/android/arch/lifecycle/ProcessOwnerTest.java
@@ -31,6 +31,7 @@ import android.arch.lifecycle.Lifecycle.Event;
import android.arch.lifecycle.testapp.NavigationDialogActivity;
import android.arch.lifecycle.testapp.NavigationTestActivityFirst;
import android.arch.lifecycle.testapp.NavigationTestActivitySecond;
+import android.arch.lifecycle.testapp.NonSupportActivity;
import android.content.Context;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
@@ -95,6 +96,22 @@ public class ProcessOwnerTest {
}
@Test
+ public void testNavigationToNonSupport() throws Throwable {
+ FragmentActivity firstActivity = setupObserverOnResume();
+ Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+ NonSupportActivity.class.getCanonicalName(), null, false);
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.addMonitor(monitor);
+
+ Intent intent = new Intent(firstActivity, NonSupportActivity.class);
+ firstActivity.finish();
+ firstActivity.startActivity(intent);
+ NonSupportActivity secondActivity = (NonSupportActivity) monitor.waitForActivity();
+ assertThat("Failed to navigate", secondActivity, notNullValue());
+ checkProcessObserverSilent(secondActivity);
+ }
+
+ @Test
public void testRecreation() throws Throwable {
FragmentActivity activity = setupObserverOnResume();
FragmentActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
@@ -164,4 +181,11 @@ public class ProcessOwnerTest {
activityTestRule.runOnUiThread(() ->
ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
}
+
+ private void checkProcessObserverSilent(NonSupportActivity activity) throws Throwable {
+ assertThat(activity.awaitResumedState(), is(true));
+ assertThat(mObserver.mChangedState, is(false));
+ activityTestRule.runOnUiThread(() ->
+ ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
+ }
}
diff --git a/android/arch/lifecycle/ReportFragment.java b/android/arch/lifecycle/ReportFragment.java
index 3e4ece82..16a89ce8 100644
--- a/android/arch/lifecycle/ReportFragment.java
+++ b/android/arch/lifecycle/ReportFragment.java
@@ -28,7 +28,6 @@ import android.support.annotation.RestrictTo;
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class ReportFragment extends Fragment {
-
private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
+ ".LifecycleDispatcher.report_fragment_tag";
diff --git a/android/arch/lifecycle/TestUtils.java b/android/arch/lifecycle/TestUtils.java
index f0214bfb..f7f9bbe5 100644
--- a/android/arch/lifecycle/TestUtils.java
+++ b/android/arch/lifecycle/TestUtils.java
@@ -16,16 +16,35 @@
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.DESTROYED;
import static android.arch.lifecycle.Lifecycle.State.RESUMED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
+import android.arch.lifecycle.testapp.TestEvent;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
-import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.Pair;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
class TestUtils {
@@ -61,23 +80,88 @@ class TestUtils {
return result;
}
- static void waitTillResumed(final FragmentActivity a, ActivityTestRule<?> activityRule)
+ static void waitTillCreated(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ throws Throwable {
+ waitTillState(owner, activityRule, CREATED);
+ }
+
+ static void waitTillStarted(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ throws Throwable {
+ waitTillState(owner, activityRule, STARTED);
+ }
+
+ static void waitTillResumed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ throws Throwable {
+ waitTillState(owner, activityRule, RESUMED);
+ }
+
+ static void waitTillDestroyed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+ throws Throwable {
+ waitTillState(owner, activityRule, DESTROYED);
+ }
+
+ static void waitTillState(final LifecycleOwner owner, ActivityTestRule<?> activityRule,
+ Lifecycle.State state)
throws Throwable {
final CountDownLatch latch = new CountDownLatch(1);
activityRule.runOnUiThread(() -> {
- Lifecycle.State currentState = a.getLifecycle().getCurrentState();
- if (currentState == RESUMED) {
+ Lifecycle.State currentState = owner.getLifecycle().getCurrentState();
+ if (currentState == state) {
latch.countDown();
+ } else {
+ owner.getLifecycle().addObserver(new LifecycleObserver() {
+ @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+ public void onStateChanged(LifecycleOwner provider) {
+ if (provider.getLifecycle().getCurrentState() == state) {
+ latch.countDown();
+ provider.getLifecycle().removeObserver(this);
+ }
+ }
+ });
}
- a.getLifecycle().addObserver(new LifecycleObserver() {
- @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
- public void onStateChanged(LifecycleOwner provider) {
- latch.countDown();
- provider.getLifecycle().removeObserver(this);
- }
- });
});
- latch.await();
+ boolean latchResult = latch.await(1, TimeUnit.MINUTES);
+ assertThat("expected " + state + " never happened. Current state:"
+ + owner.getLifecycle().getCurrentState(), latchResult, is(true));
+
+ // wait for another loop to ensure all observers are called
+ activityRule.runOnUiThread(() -> {
+ // do nothing
+ });
}
+ @SafeVarargs
+ static <T> List<T> flatMap(List<T>... items) {
+ ArrayList<T> result = new ArrayList<>();
+ for (List<T> item : items) {
+ result.addAll(item);
+ }
+ return result;
+ }
+
+ /**
+ * Event tuples of {@link TestEvent} and {@link Lifecycle.Event}
+ * in the order they should arrive.
+ */
+ @SuppressWarnings("unchecked")
+ static class OrderedTuples {
+ static final List<Pair<TestEvent, Lifecycle.Event>> CREATE =
+ Arrays.asList(new Pair(OWNER_CALLBACK, ON_CREATE),
+ new Pair(LIFECYCLE_EVENT, ON_CREATE));
+ static final List<Pair<TestEvent, Lifecycle.Event>> START =
+ Arrays.asList(new Pair(OWNER_CALLBACK, ON_START),
+ new Pair(LIFECYCLE_EVENT, ON_START));
+ static final List<Pair<TestEvent, Lifecycle.Event>> RESUME =
+ Arrays.asList(new Pair(OWNER_CALLBACK, ON_RESUME),
+ new Pair(LIFECYCLE_EVENT, ON_RESUME));
+ static final List<Pair<TestEvent, Lifecycle.Event>> PAUSE =
+ Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_PAUSE),
+ new Pair(OWNER_CALLBACK, ON_PAUSE));
+ static final List<Pair<TestEvent, Lifecycle.Event>> STOP =
+ Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_STOP),
+ new Pair(OWNER_CALLBACK, ON_STOP));
+ static final List<Pair<TestEvent, Lifecycle.Event>> DESTROY =
+ Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_DESTROY),
+ new Pair(OWNER_CALLBACK, ON_DESTROY));
+ }
}
diff --git a/android/arch/lifecycle/Transformations.java b/android/arch/lifecycle/Transformations.java
index 9ce9cbb7..c735f8ba 100644
--- a/android/arch/lifecycle/Transformations.java
+++ b/android/arch/lifecycle/Transformations.java
@@ -18,6 +18,7 @@ package android.arch.lifecycle;
import android.arch.core.util.Function;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
@@ -60,7 +61,8 @@ public class Transformations {
* @return a LiveData which emits resulting values
*/
@MainThread
- public static <X, Y> LiveData<Y> map(LiveData<X> source, final Function<X, Y> func) {
+ public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
+ @NonNull final Function<X, Y> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
@@ -120,8 +122,8 @@ public class Transformations {
* @param <Y> a type of resulting LiveData
*/
@MainThread
- public static <X, Y> LiveData<Y> switchMap(LiveData<X> trigger,
- final Function<X, LiveData<Y>> func) {
+ public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
+ @NonNull final Function<X, LiveData<Y>> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(trigger, new Observer<X>() {
LiveData<Y> mSource;
diff --git a/android/arch/lifecycle/ViewModelProvider.java b/android/arch/lifecycle/ViewModelProvider.java
index 7ef591f3..29cbab8e 100644
--- a/android/arch/lifecycle/ViewModelProvider.java
+++ b/android/arch/lifecycle/ViewModelProvider.java
@@ -43,7 +43,8 @@ public class ViewModelProvider {
* @param <T> The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
- <T extends ViewModel> T create(Class<T> modelClass);
+ @NonNull
+ <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
private final Factory mFactory;
@@ -70,7 +71,7 @@ public class ViewModelProvider {
* @param factory factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
- public ViewModelProvider(ViewModelStore store, Factory factory) {
+ public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}
@@ -88,7 +89,8 @@ public class ViewModelProvider {
* @param <T> The type parameter for the ViewModel.
* @return A ViewModel that is an instance of the given type {@code T}.
*/
- public <T extends ViewModel> T get(Class<T> modelClass) {
+ @NonNull
+ public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
@@ -136,8 +138,9 @@ public class ViewModelProvider {
*/
public static class NewInstanceFactory implements Factory {
+ @NonNull
@Override
- public <T extends ViewModel> T create(Class<T> modelClass) {
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.newInstance();
diff --git a/android/arch/lifecycle/ViewModelProviders.java b/android/arch/lifecycle/ViewModelProviders.java
index 746162a9..b4b20aa4 100644
--- a/android/arch/lifecycle/ViewModelProviders.java
+++ b/android/arch/lifecycle/ViewModelProviders.java
@@ -139,8 +139,9 @@ public class ViewModelProviders {
mApplication = application;
}
+ @NonNull
@Override
- public <T extends ViewModel> T create(Class<T> modelClass) {
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//noinspection TryWithIdenticalCatches
try {
diff --git a/android/arch/lifecycle/ViewModelStoreOwner.java b/android/arch/lifecycle/ViewModelStoreOwner.java
index 50583056..e26fa325 100644
--- a/android/arch/lifecycle/ViewModelStoreOwner.java
+++ b/android/arch/lifecycle/ViewModelStoreOwner.java
@@ -16,6 +16,8 @@
package android.arch.lifecycle;
+import android.support.annotation.NonNull;
+
/**
* A scope that owns {@link ViewModelStore}.
* <p>
@@ -30,5 +32,6 @@ public interface ViewModelStoreOwner {
*
* @return a {@code ViewModelStore}
*/
+ @NonNull
ViewModelStore getViewModelStore();
}
diff --git a/android/arch/lifecycle/ViewModelStores.java b/android/arch/lifecycle/ViewModelStores.java
index 8c17dd98..d7d769d6 100644
--- a/android/arch/lifecycle/ViewModelStores.java
+++ b/android/arch/lifecycle/ViewModelStores.java
@@ -19,6 +19,7 @@ package android.arch.lifecycle;
import static android.arch.lifecycle.HolderFragment.holderFragmentFor;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
@@ -38,7 +39,7 @@ public class ViewModelStores {
* @return a {@code ViewModelStore}
*/
@MainThread
- public static ViewModelStore of(FragmentActivity activity) {
+ public static ViewModelStore of(@NonNull FragmentActivity activity) {
return holderFragmentFor(activity).getViewModelStore();
}
@@ -49,7 +50,7 @@ public class ViewModelStores {
* @return a {@code ViewModelStore}
*/
@MainThread
- public static ViewModelStore of(Fragment fragment) {
+ public static ViewModelStore of(@NonNull Fragment fragment) {
return holderFragmentFor(fragment).getViewModelStore();
}
}
diff --git a/android/arch/lifecycle/testapp/CollectingActivity.java b/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
index 6e243b6c..4213cab9 100644
--- a/android/arch/lifecycle/testapp/CollectingActivity.java
+++ b/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
@@ -17,21 +17,20 @@
package android.arch.lifecycle.testapp;
import android.arch.lifecycle.Lifecycle;
-import android.util.Pair;
+import android.arch.lifecycle.LifecycleOwner;
+import android.support.v4.util.Pair;
import java.util.List;
/**
* For activities that collect their events.
*/
-public interface CollectingActivity {
- long TIMEOUT = 5;
-
+public interface CollectingLifecycleOwner extends LifecycleOwner {
/**
- * Return collected events
+ * Return a copy of currently collected events
*
* @return The list of collected events.
* @throws InterruptedException
*/
- List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents() throws InterruptedException;
+ List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents();
}
diff --git a/android/arch/lifecycle/testapp/CollectingSupportActivity.java b/android/arch/lifecycle/testapp/CollectingSupportActivity.java
new file mode 100644
index 00000000..f38d4224
--- /dev/null
+++ b/android/arch/lifecycle/testapp/CollectingSupportActivity.java
@@ -0,0 +1,113 @@
+/*
+ * 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.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.util.Pair;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * LifecycleRegistryOwner that extends FragmentActivity.
+ */
+public class CollectingSupportActivity extends FragmentActivity implements
+ CollectingLifecycleOwner {
+
+ private final List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>();
+ private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+ private CountDownLatch mSavedStateLatch = new CountDownLatch(1);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ FrameLayout layout = new FrameLayout(this);
+ layout.setId(R.id.fragment_container);
+ setContentView(layout);
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_CREATE));
+ getLifecycle().addObserver(mTestObserver);
+ }
+
+ /**
+ * replaces the main content fragment w/ the given fragment.
+ */
+ public void replaceFragment(Fragment fragment) {
+ getSupportFragmentManager()
+ .beginTransaction()
+ .add(R.id.fragment_container, fragment)
+ .commitNow();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_START));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_RESUME));
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_DESTROY));
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_STOP));
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_PAUSE));
+ // helps with less flaky API 16 tests.
+ overridePendingTransition(0, 0);
+ }
+
+ @Override
+ public List<Pair<TestEvent, Event>> copyCollectedEvents() {
+ return new ArrayList<>(mCollectedEvents);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mSavedStateLatch.countDown();
+ }
+
+ /**
+ * Waits for onSaveInstanceState to be called.
+ */
+ public boolean waitForStateSave(@SuppressWarnings("SameParameterValue") int seconds)
+ throws InterruptedException {
+ return mSavedStateLatch.await(seconds, TimeUnit.SECONDS);
+ }
+}
diff --git a/android/arch/lifecycle/testapp/CollectingSupportFragment.java b/android/arch/lifecycle/testapp/CollectingSupportFragment.java
new file mode 100644
index 00000000..9bbbe165
--- /dev/null
+++ b/android/arch/lifecycle/testapp/CollectingSupportFragment.java
@@ -0,0 +1,104 @@
+/*
+ * 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.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import android.annotation.SuppressLint;
+import android.arch.lifecycle.Lifecycle;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A support fragment that collects all of its events.
+ */
+@SuppressLint("ValidFragment")
+public class CollectingSupportFragment extends Fragment implements CollectingLifecycleOwner {
+ private final List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents =
+ new ArrayList<>();
+ private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
+ getLifecycle().addObserver(mTestObserver);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ //noinspection ConstantConditions
+ FrameLayout layout = new FrameLayout(container.getContext());
+ layout.setId(R.id.child_fragment_container);
+ return layout;
+ }
+
+ /**
+ * Runs a replace fragment transaction with 'fragment' on this Fragment.
+ */
+ public void replaceFragment(Fragment fragment) {
+ getChildFragmentManager()
+ .beginTransaction()
+ .add(R.id.child_fragment_container, fragment)
+ .commitNow();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
+ }
+
+ @Override
+ public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
+ return new ArrayList<>(mCollectedEvents);
+ }
+}
diff --git a/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java b/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
index d8f4fb39..cdf577c1 100644
--- a/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
+++ b/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
@@ -16,27 +16,29 @@
package android.arch.lifecycle.testapp;
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
+import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LifecycleRegistryOwner;
import android.os.Bundle;
-import android.util.Pair;
+import android.support.annotation.NonNull;
+import android.support.v4.util.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
/**
* LifecycleRegistryOwner that extends framework activity.
*/
+@SuppressWarnings("deprecation")
public class FrameworkLifecycleRegistryActivity extends Activity implements
- LifecycleRegistryOwner, CollectingActivity {
+ LifecycleRegistryOwner, CollectingLifecycleOwner {
private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+ @NonNull
@Override
public LifecycleRegistry getLifecycle() {
return mLifecycleRegistry;
@@ -49,49 +51,43 @@ public class FrameworkLifecycleRegistryActivity extends Activity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
getLifecycle().addObserver(mTestObserver);
}
@Override
protected void onStart() {
super.onStart();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
}
@Override
protected void onResume() {
super.onResume();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
- finish();
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
}
@Override
protected void onDestroy() {
super.onDestroy();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
mLatch.countDown();
}
@Override
protected void onStop() {
super.onStop();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
}
@Override
protected void onPause() {
super.onPause();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
+ mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
}
- /**
- * awaits for all events and returns them.
- */
@Override
- public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents()
- throws InterruptedException {
- mLatch.await(TIMEOUT, TimeUnit.SECONDS);
- return mCollectedEvents;
+ public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
+ return new ArrayList<>(mCollectedEvents);
}
}
diff --git a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
deleted file mode 100644
index 5f33c282..00000000
--- a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
+++ /dev/null
@@ -1,88 +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.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
-
-import android.arch.lifecycle.Lifecycle;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.util.Pair;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity for testing full lifecycle
- */
-public class FullLifecycleTestActivity extends FragmentActivity implements CollectingActivity {
-
- private List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = new ArrayList<>();
- private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
- private CountDownLatch mLatch = new CountDownLatch(1);
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
- getLifecycle().addObserver(mTestObserver);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
- finish();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
- mLatch.countDown();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
- }
-
- /**
- * awaits for all events and returns them.
- */
- @Override
- public List<Pair<TestEvent, Lifecycle.Event>> waitForCollectedEvents()
- throws InterruptedException {
- mLatch.await(TIMEOUT, TimeUnit.SECONDS);
- return mCollectedEvents;
- }
-}
diff --git a/android/arch/lifecycle/testapp/MainActivity.java b/android/arch/lifecycle/testapp/MainActivity.java
deleted file mode 100644
index b9d59142..00000000
--- a/android/arch/lifecycle/testapp/MainActivity.java
+++ /dev/null
@@ -1,31 +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.arch.lifecycle.testapp;
-
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-
-/**
- * Simple test activity
- */
-public class MainActivity extends FragmentActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity);
- }
-}
diff --git a/android/arch/lifecycle/testapp/NavigationDialogActivity.java b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
index 0ae94033..7d53528f 100644
--- a/android/arch/lifecycle/testapp/NavigationDialogActivity.java
+++ b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
@@ -22,4 +22,10 @@ import android.support.v4.app.FragmentActivity;
* an activity with Dialog theme.
*/
public class NavigationDialogActivity extends FragmentActivity {
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // helps with less flaky API 16 tests
+ overridePendingTransition(0, 0);
+ }
}
diff --git a/android/arch/lifecycle/testapp/NonSupportActivity.java b/android/arch/lifecycle/testapp/NonSupportActivity.java
new file mode 100644
index 00000000..835d846a
--- /dev/null
+++ b/android/arch/lifecycle/testapp/NonSupportActivity.java
@@ -0,0 +1,85 @@
+/*
+ * 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.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Activity which doesn't extend FragmentActivity, to test ProcessLifecycleOwner because it
+ * should work anyway.
+ */
+public class NonSupportActivity extends Activity {
+
+ private static final int TIMEOUT = 1; //secs
+ private final Lock mLock = new ReentrantLock();
+ private Condition mIsResumedCondition = mLock.newCondition();
+ private boolean mIsResumed = false;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mLock.lock();
+ try {
+ mIsResumed = true;
+ mIsResumedCondition.signalAll();
+ } finally {
+ mLock.unlock();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mLock.lock();
+ try {
+ mIsResumed = false;
+ } finally {
+ mLock.unlock();
+ }
+ }
+
+ /**
+ * awaits resumed state
+ * @return
+ * @throws InterruptedException
+ */
+ public boolean awaitResumedState() throws InterruptedException {
+ mLock.lock();
+ try {
+ while (!mIsResumed) {
+ if (!mIsResumedCondition.await(TIMEOUT, TimeUnit.SECONDS)) {
+ return false;
+ }
+ }
+ return true;
+ } finally {
+ mLock.unlock();
+ }
+ }
+}
diff --git a/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java b/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
deleted file mode 100644
index c46c6d3e..00000000
--- a/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
-
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleRegistry;
-import android.arch.lifecycle.LifecycleRegistryOwner;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.util.Pair;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * LifecycleRegistryOwner that extends FragmentActivity.
- */
-public class SupportLifecycleRegistryActivity extends FragmentActivity implements
- LifecycleRegistryOwner, CollectingActivity {
- private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
- @Override
- public LifecycleRegistry getLifecycle() {
- return mLifecycleRegistry;
- }
-
- private List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>();
- private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
- private CountDownLatch mLatch = new CountDownLatch(1);
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_CREATE));
- getLifecycle().addObserver(mTestObserver);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_START));
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_RESUME));
- finish();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_DESTROY));
- mLatch.countDown();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_STOP));
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_PAUSE));
- }
-
- /**
- * awaits for all events and returns them.
- */
- @Override
- public List<Pair<TestEvent, Event>> waitForCollectedEvents() throws InterruptedException {
- mLatch.await(TIMEOUT, TimeUnit.SECONDS);
- return mCollectedEvents;
- }
-}
diff --git a/android/arch/lifecycle/testapp/TestEvent.java b/android/arch/lifecycle/testapp/TestEvent.java
index 0929f84a..788045a2 100644
--- a/android/arch/lifecycle/testapp/TestEvent.java
+++ b/android/arch/lifecycle/testapp/TestEvent.java
@@ -17,6 +17,6 @@
package android.arch.lifecycle.testapp;
public enum TestEvent {
- ACTIVITY_CALLBACK,
- LIFECYCLE_EVENT
+ OWNER_CALLBACK,
+ LIFECYCLE_EVENT,
}
diff --git a/android/arch/lifecycle/testapp/TestObserver.java b/android/arch/lifecycle/testapp/TestObserver.java
index c6112396..00b8e16d 100644
--- a/android/arch/lifecycle/testapp/TestObserver.java
+++ b/android/arch/lifecycle/testapp/TestObserver.java
@@ -28,7 +28,7 @@ import android.arch.lifecycle.Lifecycle.Event;
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.OnLifecycleEvent;
-import android.util.Pair;
+import android.support.v4.util.Pair;
import java.util.List;
diff --git a/android/arch/paging/BoundedDataSource.java b/android/arch/paging/BoundedDataSource.java
index 664ab16c..06564907 100644
--- a/android/arch/paging/BoundedDataSource.java
+++ b/android/arch/paging/BoundedDataSource.java
@@ -21,7 +21,6 @@ import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
@@ -75,7 +74,6 @@ public abstract class BoundedDataSource<Value> extends PositionalDataSource<Valu
if (result.size() != loadSize) {
throw new IllegalStateException("invalid number of items returned.");
}
- Collections.reverse(result);
}
return result;
}
diff --git a/android/arch/paging/ContiguousDataSource.java b/android/arch/paging/ContiguousDataSource.java
index afcc208c..be9da200 100644
--- a/android/arch/paging/ContiguousDataSource.java
+++ b/android/arch/paging/ContiguousDataSource.java
@@ -26,21 +26,65 @@ import java.util.List;
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
- /**
- * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED.
- *
- * @return number of items that this DataSource can provide in total, or COUNT_UNDEFINED
- * if difficult or undesired to compute.
- */
- public int countItems() {
- return COUNT_UNDEFINED;
- }
-
@Override
boolean isContiguous() {
return true;
}
+ void loadInitial(Key key, int pageSize, boolean enablePlaceholders,
+ PageResult.Receiver<Key, Value> receiver) {
+ NullPaddedList<Value> initial = loadInitial(key, pageSize, enablePlaceholders);
+ if (initial != null) {
+ receiver.onPageResult(new PageResult<>(
+ PageResult.INIT,
+ new Page<Key, Value>(initial.mList),
+ initial.getLeadingNullCount(),
+ initial.getTrailingNullCount(),
+ initial.getPositionOffset()));
+ } else {
+ receiver.onPageResult(new PageResult<Key, Value>(
+ PageResult.INIT, null, 0, 0, 0));
+ }
+ }
+
+ void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
+ PageResult.Receiver<Key, Value> receiver) {
+ List<Value> list = loadAfter(currentEndIndex, currentEndItem, pageSize);
+
+ Page<Key, Value> page = list != null
+ ? new Page<Key, Value>(list) : null;
+
+ receiver.postOnPageResult(new PageResult<>(
+ PageResult.APPEND, page, 0, 0, 0));
+ }
+
+ void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
+ PageResult.Receiver<Key, Value> receiver) {
+ List<Value> list = loadBefore(currentBeginIndex, currentBeginItem, pageSize);
+
+ Page<Key, Value> page = list != null
+ ? new Page<Key, Value>(list) : null;
+
+ receiver.postOnPageResult(new PageResult<>(
+ PageResult.PREPEND, page, 0, 0, 0));
+ }
+
+ /**
+ * Get the key from either the position, or item, or null if position/item invalid.
+ * <p>
+ * Position may not match passed item's position - if trying to query the key from a position
+ * that isn't yet loaded, a fallback item (last loaded item accessed) will be passed.
+ */
+ abstract Key getKey(int position, Value item);
+
+ @Nullable
+ abstract List<Value> loadAfterImpl(int currentEndIndex,
+ @NonNull Value currentEndItem, int pageSize);
+
+ @Nullable
+ abstract List<Value> loadBeforeImpl(int currentBeginIndex,
+ @NonNull Value currentBeginItem, int pageSize);
+
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@@ -48,21 +92,7 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V
public abstract NullPaddedList<Value> loadInitial(
Key key, int initialLoadSize, boolean enablePlaceholders);
- /**
- * Load data after the given position / item.
- * <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 number loaded than reduce.
- *
- * @param currentEndIndex Load items after this index, starting with currentEndIndex + 1.
- * @param currentEndItem Load items after this item, can be used for precise querying based on
- * item contents.
- * @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
- */
+ /** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
@@ -78,24 +108,7 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V
return list;
}
- @Nullable
- abstract List<Value> loadAfterImpl(int currentEndIndex,
- @NonNull Value currentEndItem, int pageSize);
-
- /**
- * Load data before the given position / item.
- * <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 number loaded than reduce.
- *
- * @param currentBeginIndex Load items before this index, starting with currentBeginIndex - 1.
- * @param currentBeginItem Load items after this item, can be used for precise querying based
- * on item contents.
- * @param pageSize Suggested number of items to load.
- * @return List of items, in descending order, starting at position currentBeginIndex - 1.
- *
- * @hide
- */
+ /** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
@@ -111,15 +124,4 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V
return list;
}
-
- @Nullable
- abstract List<Value> loadBeforeImpl(int currentBeginIndex,
- @NonNull Value currentBeginItem, int pageSize);
-
- /**
- * Get the key from either the position, or item. Position may not match passed item's position,
- * if trying to query the key from a position that isn't yet loaded, so a fallback item must be
- * used.
- */
- abstract Key getKey(int position, Value item);
}
diff --git a/android/arch/paging/ContiguousPagedList.java b/android/arch/paging/ContiguousPagedList.java
index d8907c3b..7835dbe3 100644
--- a/android/arch/paging/ContiguousPagedList.java
+++ b/android/arch/paging/ContiguousPagedList.java
@@ -16,101 +16,136 @@
package android.arch.paging;
+import android.support.annotation.AnyThread;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class ContiguousPagedList<T> extends NullPaddedList<T> {
-
- private final ContiguousDataSource<?, T> mDataSource;
- private final Executor mMainThreadExecutor;
- private final Executor mBackgroundThreadExecutor;
- private final Config mConfig;
+class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback {
+ private final ContiguousDataSource<K, V> mDataSource;
private boolean mPrependWorkerRunning = false;
private boolean mAppendWorkerRunning = false;
private int mPrependItemsRequested = 0;
private int mAppendItemsRequested = 0;
- private int mLastLoad = 0;
- private T mLastItem = null;
+ @SuppressWarnings("unchecked")
+ private final PagedStorage<K, V> mKeyedStorage = (PagedStorage<K, V>) mStorage;
+
+ private final PageResult.Receiver<K, V> mReceiver = new PageResult.Receiver<K, V>() {
+ @AnyThread
+ @Override
+ public void postOnPageResult(@NonNull final PageResult<K, V> pageResult) {
+ // NOTE: if we're already on main thread, this can delay page receive by a frame
+ mMainThreadExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ onPageResult(pageResult);
+ }
+ });
+ }
- private AtomicBoolean mDetached = new AtomicBoolean(false);
+ @MainThread
+ @Override
+ public void onPageResult(@NonNull PageResult<K, V> pageResult) {
+ if (pageResult.page == null) {
+ detach();
+ return;
+ }
- private ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+ if (isDetached()) {
+ // No op, have detached
+ return;
+ }
- @WorkerThread
- <K> ContiguousPagedList(@NonNull ContiguousDataSource<K, T> dataSource,
+ Page<K, V> page = pageResult.page;
+ if (pageResult.type == PageResult.INIT) {
+ mKeyedStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
+ pageResult.positionOffset, ContiguousPagedList.this);
+ notifyInserted(0, mKeyedStorage.size());
+ } else if (pageResult.type == PageResult.APPEND) {
+ mKeyedStorage.appendPage(page, ContiguousPagedList.this);
+ } else if (pageResult.type == PageResult.PREPEND) {
+ mKeyedStorage.prependPage(page, ContiguousPagedList.this);
+ }
+ }
+ };
+
+ ContiguousPagedList(
+ @NonNull ContiguousDataSource<K, V> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
- Config config,
- @Nullable K key) {
- super();
-
+ @NonNull Config config,
+ final @Nullable K key) {
+ super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor, config);
mDataSource = dataSource;
- mMainThreadExecutor = mainThreadExecutor;
- mBackgroundThreadExecutor = backgroundThreadExecutor;
- mConfig = config;
- NullPaddedList<T> initialState = dataSource.loadInitial(
- key, config.mInitialLoadSizeHint, config.mEnablePlaceholders);
-
- if (initialState != null) {
- mPositionOffset = initialState.getPositionOffset();
-
- mLeadingNullCount = initialState.getLeadingNullCount();
- mList = new ArrayList<>(initialState.mList);
- mTrailingNullCount = initialState.getTrailingNullCount();
-
- if (initialState.getLeadingNullCount() == 0
- && initialState.getTrailingNullCount() == 0
- && config.mPrefetchDistance < 1) {
- throw new IllegalArgumentException("Null padding is required to support the 0"
- + " prefetch case - require either null items or prefetching to fetch"
- + " beyond initial load.");
- }
- if (initialState.size() != 0) {
- mLastLoad = mLeadingNullCount + mList.size() / 2;
- mLastItem = mList.get(mList.size() / 2);
- }
- } else {
- mList = new ArrayList<>();
- detach();
- }
- if (mList.size() == 0) {
- // Empty initial state, so don't try and fetch data.
- mPrependWorkerRunning = true;
- mAppendWorkerRunning = true;
- }
+ // blocking init just triggers the initial load on the construction thread -
+ // Could still be posted with callback, if desired.
+ mDataSource.loadInitial(key,
+ mConfig.mInitialLoadSizeHint,
+ mConfig.mEnablePlaceholders,
+ mReceiver);
}
+ @MainThread
@Override
- public T get(int index) {
- T item = super.get(index);
- if (item != null) {
- mLastItem = item;
+ void dispatchUpdatesSinceSnapshot(
+ @NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) {
+
+ final PagedStorage<?, V> snapshot = pagedListSnapshot.mStorage;
+
+ final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended();
+ final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended();
+
+ final int previousTrailing = snapshot.getTrailingNullCount();
+ final int previousLeading = snapshot.getLeadingNullCount();
+
+ // Validate that the snapshot looks like a previous version of this list - if it's not,
+ // we can't be sure we'll dispatch callbacks safely
+ if (newlyAppended < 0
+ || newlyPrepended < 0
+ || mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0)
+ || mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0)
+ || (mStorage.getStorageCount()
+ != snapshot.getStorageCount() + newlyAppended + newlyPrepended)) {
+ throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
+ + " to be a snapshot of this PagedList");
+ }
+
+ if (newlyAppended != 0) {
+ final int changedCount = Math.min(previousTrailing, newlyAppended);
+ final int addedCount = newlyAppended - changedCount;
+
+ final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount();
+ if (changedCount != 0) {
+ callback.onChanged(endPosition, changedCount);
+ }
+ if (addedCount != 0) {
+ callback.onInserted(endPosition + changedCount, addedCount);
+ }
+ }
+ if (newlyPrepended != 0) {
+ final int changedCount = Math.min(previousLeading, newlyPrepended);
+ final int addedCount = newlyPrepended - changedCount;
+
+ if (changedCount != 0) {
+ callback.onChanged(previousLeading, changedCount);
+ }
+ if (addedCount != 0) {
+ callback.onInserted(0, addedCount);
+ }
}
- return item;
}
+ @MainThread
@Override
- public void loadAround(int index) {
- mLastLoad = index + mPositionOffset;
-
- int prependItems = mConfig.mPrefetchDistance - (index - mLeadingNullCount);
- int appendItems = index + mConfig.mPrefetchDistance - (mLeadingNullCount + mList.size());
+ protected void loadAroundInternal(int index) {
+ int prependItems = mConfig.mPrefetchDistance - (index - mStorage.getLeadingNullCount());
+ int appendItems = index + mConfig.mPrefetchDistance
+ - (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
if (mPrependItemsRequested > 0) {
@@ -123,21 +158,6 @@ class ContiguousPagedList<T> extends NullPaddedList<T> {
}
}
- @Override
- public int getLoadedCount() {
- return mList.size();
- }
-
- @Override
- public int getLeadingNullCount() {
- return mLeadingNullCount;
- }
-
- @Override
- public int getTrailingNullCount() {
- return mTrailingNullCount;
- }
-
@MainThread
private void schedulePrepend() {
if (mPrependWorkerRunning) {
@@ -145,29 +165,17 @@ class ContiguousPagedList<T> extends NullPaddedList<T> {
}
mPrependWorkerRunning = true;
- final int position = mLeadingNullCount + mPositionOffset;
- final T item = mList.get(0);
+ final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset();
+
+ // safe to access first item here - mStorage can't be empty if we're prepending
+ final V item = mStorage.getFirstContiguousItem();
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
- if (mDetached.get()) {
+ if (isDetached()) {
return;
}
-
- final List<T> data = mDataSource.loadBefore(position, item, mConfig.mPageSize);
- if (data != null) {
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (mDetached.get()) {
- return;
- }
- prependImpl(data);
- }
- });
- } else {
- detach();
- }
+ mDataSource.loadBefore(position, item, mConfig.mPageSize, mReceiver);
}
});
}
@@ -179,56 +187,44 @@ class ContiguousPagedList<T> extends NullPaddedList<T> {
}
mAppendWorkerRunning = true;
- final int position = mLeadingNullCount + mList.size() - 1 + mPositionOffset;
- final T item = mList.get(mList.size() - 1);
+ final int position = mStorage.getLeadingNullCount()
+ + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();
+
+ // safe to access first item here - mStorage can't be empty if we're appending
+ final V item = mStorage.getLastContiguousItem();
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
- if (mDetached.get()) {
+ if (isDetached()) {
return;
}
-
- final List<T> data = mDataSource.loadAfter(position, item, mConfig.mPageSize);
- if (data != null) {
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (mDetached.get()) {
- return;
- }
- appendImpl(data);
- }
- });
- } else {
- detach();
- }
+ mDataSource.loadAfter(position, item, mConfig.mPageSize, mReceiver);
}
});
}
- @MainThread
- private void prependImpl(List<T> before) {
- final int count = before.size();
- if (count == 0) {
- // Nothing returned from source, stop loading in this direction
- return;
- }
-
- Collections.reverse(before);
- mList.addAll(0, before);
-
- final int changedCount = Math.min(mLeadingNullCount, count);
- final int addedCount = count - changedCount;
+ @Override
+ boolean isContiguous() {
+ return true;
+ }
- if (changedCount != 0) {
- mLeadingNullCount -= changedCount;
- }
- mPositionOffset -= addedCount;
- mNumberPrepended += count;
+ @Nullable
+ @Override
+ public Object getLastKey() {
+ return mDataSource.getKey(mLastLoad, mLastItem);
+ }
+ @MainThread
+ @Override
+ public void onInitialized(int count) {
+ notifyInserted(0, count);
+ }
- // only try to post more work after fully prepended (with offsets / null counts updated)
- mPrependItemsRequested -= count;
+ @MainThread
+ @Override
+ public void onPagePrepended(int leadingNulls, int changedCount, int addedCount) {
+ // consider whether to post more work, now that a page is fully prepended
+ mPrependItemsRequested = mPrependItemsRequested - changedCount - addedCount;
mPrependWorkerRunning = false;
if (mPrependItemsRequested > 0) {
// not done prepending, keep going
@@ -236,39 +232,16 @@ class ContiguousPagedList<T> extends NullPaddedList<T> {
}
// finally dispatch callbacks, after prepend may have already been scheduled
- for (WeakReference<Callback> weakRef : mCallbacks) {
- Callback callback = weakRef.get();
- if (callback != null) {
- if (changedCount != 0) {
- callback.onChanged(mLeadingNullCount, changedCount);
- }
- if (addedCount != 0) {
- callback.onInserted(0, addedCount);
- }
- }
- }
+ notifyChanged(leadingNulls, changedCount);
+ notifyInserted(0, addedCount);
}
@MainThread
- private void appendImpl(List<T> after) {
- final int count = after.size();
- if (count == 0) {
- // Nothing returned from source, stop loading in this direction
- return;
- }
-
- mList.addAll(after);
-
- final int changedCount = Math.min(mTrailingNullCount, count);
- final int addedCount = count - changedCount;
-
- if (changedCount != 0) {
- mTrailingNullCount -= changedCount;
- }
- mNumberAppended += count;
+ @Override
+ public void onPageAppended(int endPosition, int changedCount, int addedCount) {
+ // consider whether to post more work, now that a page is fully appended
- // only try to post more work after fully appended (with null counts updated)
- mAppendItemsRequested -= count;
+ mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
mAppendWorkerRunning = false;
if (mAppendItemsRequested > 0) {
// not done appending, keep going
@@ -276,100 +249,19 @@ class ContiguousPagedList<T> extends NullPaddedList<T> {
}
// finally dispatch callbacks, after append may have already been scheduled
- for (WeakReference<Callback> weakRef : mCallbacks) {
- Callback callback = weakRef.get();
- if (callback != null) {
- final int endPosition = mLeadingNullCount + mList.size() - count;
- if (changedCount != 0) {
- callback.onChanged(endPosition, changedCount);
- }
- if (addedCount != 0) {
- callback.onInserted(endPosition + changedCount, addedCount);
- }
- }
- }
- }
-
- @Override
- public boolean isImmutable() {
- // TODO: return true if had nulls, and now getLoadedCount() == size(). Is that safe?
- // Currently we don't prevent DataSources from returning more items than their null counts
- return isDetached();
- }
-
- @Override
- public void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
- @NonNull Callback callback) {
- NullPaddedList<T> snapshot = (NullPaddedList<T>) previousSnapshot;
- if (snapshot != this && snapshot != null) {
- final int newlyAppended = mNumberAppended - snapshot.getNumberAppended();
- final int newlyPrepended = mNumberPrepended - snapshot.getNumberPrepended();
-
- final int previousTrailing = snapshot.getTrailingNullCount();
- final int previousLeading = snapshot.getLeadingNullCount();
-
- // Validate that the snapshot looks like a previous version of this list - if it's not,
- // we can't be sure we'll dispatch callbacks safely
- if (newlyAppended < 0
- || newlyPrepended < 0
- || mTrailingNullCount != Math.max(previousTrailing - newlyAppended, 0)
- || mLeadingNullCount != Math.max(previousLeading - newlyPrepended, 0)
- || snapshot.getLoadedCount() + newlyAppended + newlyPrepended != mList.size()) {
- throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
- + " to be a snapshot of this list");
- }
-
- if (newlyAppended != 0) {
- final int changedCount = Math.min(previousTrailing, newlyAppended);
- final int addedCount = newlyAppended - changedCount;
-
- final int endPosition =
- snapshot.getLeadingNullCount() + snapshot.getLoadedCount();
- if (changedCount != 0) {
- callback.onChanged(endPosition, changedCount);
- }
- if (addedCount != 0) {
- callback.onInserted(endPosition + changedCount, addedCount);
- }
- }
- if (newlyPrepended != 0) {
- final int changedCount = Math.min(previousLeading, newlyPrepended);
- final int addedCount = newlyPrepended - changedCount;
-
- if (changedCount != 0) {
- callback.onChanged(previousLeading, changedCount);
- }
- if (addedCount != 0) {
- callback.onInserted(0, addedCount);
- }
- }
- }
- mCallbacks.add(new WeakReference<>(callback));
- }
-
- @Override
- public void removeWeakCallback(@NonNull Callback callback) {
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- Callback currentCallback = mCallbacks.get(i).get();
- if (currentCallback == null || currentCallback == callback) {
- mCallbacks.remove(i);
- }
- }
+ notifyChanged(endPosition, changedCount);
+ notifyInserted(endPosition + changedCount, addedCount);
}
+ @MainThread
@Override
- public boolean isDetached() {
- return mDetached.get();
+ public void onPagePlaceholderInserted(int pageIndex) {
+ throw new IllegalStateException("Tiled callback on ContiguousPagedList");
}
- @SuppressWarnings("WeakerAccess")
- public void detach() {
- mDetached.set(true);
- }
-
- @Nullable
+ @MainThread
@Override
- public Object getLastKey() {
- return mDataSource.getKey(mLastLoad, mLastItem);
+ public void onPageInserted(int start, int count) {
+ throw new IllegalStateException("Tiled callback on ContiguousPagedList");
}
}
diff --git a/android/arch/paging/ContiguousPagedListTest.java b/android/arch/paging/ContiguousPagedListTest.java
index ee7ea6a4..43f556a8 100644
--- a/android/arch/paging/ContiguousPagedListTest.java
+++ b/android/arch/paging/ContiguousPagedListTest.java
@@ -16,6 +16,7 @@
package android.arch.paging;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
@@ -109,6 +110,7 @@ public class ContiguousPagedListTest {
private void verifyRange(int start, int count, NullPaddedList<Item> actual) {
if (mCounted) {
+ //noinspection UnnecessaryLocalVariable
int expectedLeading = start;
int expectedTrailing = ITEMS.size() - start - count;
assertEquals(ITEMS.size(), actual.size());
@@ -132,6 +134,38 @@ public class ContiguousPagedListTest {
}
}
+ @SuppressWarnings("SuspiciousSystemArraycopy")
+ private void verifyRange(int start, int count, PagedStorage<?, Item> actual) {
+ if (mCounted) {
+ Item[] expected = new Item[ITEMS.size()];
+ System.arraycopy(ITEMS.toArray(), start, expected, start, count);
+ assertArrayEquals(expected, actual.toArray());
+
+ //noinspection UnnecessaryLocalVariable
+ int expectedLeading = start;
+ int expectedTrailing = ITEMS.size() - start - count;
+ assertEquals(ITEMS.size(), actual.size());
+ assertEquals(ITEMS.size() - expectedLeading - expectedTrailing,
+ actual.getStorageCount());
+ assertEquals(expectedLeading, actual.getLeadingNullCount());
+ assertEquals(expectedTrailing, actual.getTrailingNullCount());
+
+ } else {
+ Item[] expected = new Item[count];
+ System.arraycopy(ITEMS.toArray(), start, expected, 0, count);
+ assertArrayEquals(expected, actual.toArray());
+
+ assertEquals(count, actual.size());
+ assertEquals(actual.size(), actual.getStorageCount());
+ assertEquals(0, actual.getLeadingNullCount());
+ assertEquals(0, actual.getTrailingNullCount());
+ }
+ }
+
+ private void verifyRange(int start, int count, PagedList<Item> actual) {
+ verifyRange(start, count, actual.mStorage);
+ }
+
private void verifyCallback(PagedList.Callback callback, int countedPosition,
int uncountedPosition) {
if (mCounted) {
@@ -154,7 +188,7 @@ public class ContiguousPagedListTest {
}
- private ContiguousPagedList<Item> createCountedPagedList(
+ private ContiguousPagedList<Integer, Item> createCountedPagedList(
PagedList.Config config, int initialPosition) {
TestSource source = new TestSource();
return new ContiguousPagedList<>(
@@ -163,7 +197,7 @@ public class ContiguousPagedListTest {
initialPosition);
}
- private ContiguousPagedList<Item> createCountedPagedList(int initialPosition) {
+ private ContiguousPagedList<Integer, Item> createCountedPagedList(int initialPosition) {
return createCountedPagedList(
new PagedList.Config.Builder()
.setInitialLoadSizeHint(40)
@@ -174,8 +208,14 @@ public class ContiguousPagedListTest {
}
@Test
+ public void construct() {
+ ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(0);
+ verifyRange(0, 40, pagedList);
+ }
+
+ @Test
public void append() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(0);
+ ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(0);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
verifyRange(0, 40, pagedList);
@@ -192,7 +232,7 @@ public class ContiguousPagedListTest {
@Test
public void prepend() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(80);
+ ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(80);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
verifyRange(60, 40, pagedList);
@@ -208,7 +248,7 @@ public class ContiguousPagedListTest {
@Test
public void outwards() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(50);
+ ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(50);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
verifyRange(30, 40, pagedList);
@@ -231,7 +271,7 @@ public class ContiguousPagedListTest {
@Test
public void multiAppend() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(0);
+ ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(0);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
verifyRange(0, 40, pagedList);
@@ -248,7 +288,7 @@ public class ContiguousPagedListTest {
@Test
public void distantPrefetch() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(
+ ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(
new PagedList.Config.Builder()
.setInitialLoadSizeHint(10)
.setPageSize(10)
@@ -274,7 +314,7 @@ public class ContiguousPagedListTest {
@Test
public void appendCallbackAddedLate() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(0);
+ ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(0);
verifyRange(0, 40, pagedList);
pagedList.loadAround(35);
@@ -282,7 +322,7 @@ public class ContiguousPagedListTest {
verifyRange(0, 60, pagedList);
// snapshot at 60 items
- NullPaddedList<Item> snapshot = (NullPaddedList<Item>) pagedList.snapshot();
+ PagedList<Item> snapshot = (PagedList<Item>) pagedList.snapshot();
verifyRange(0, 60, snapshot);
@@ -300,7 +340,7 @@ public class ContiguousPagedListTest {
@Test
public void prependCallbackAddedLate() {
- ContiguousPagedList<Item> pagedList = createCountedPagedList(80);
+ ContiguousPagedList<Integer, Item> pagedList = createCountedPagedList(80);
verifyRange(60, 40, pagedList);
pagedList.loadAround(mCounted ? 65 : 5);
@@ -308,7 +348,7 @@ public class ContiguousPagedListTest {
verifyRange(40, 60, pagedList);
// snapshot at 60 items
- NullPaddedList<Item> snapshot = (NullPaddedList<Item>) pagedList.snapshot();
+ PagedList<Item> snapshot = (PagedList<Item>) pagedList.snapshot();
verifyRange(40, 60, snapshot);
diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java
index 48fbec5f..524e570a 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.NonNull;
import android.support.annotation.WorkerThread;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -60,15 +61,6 @@ public abstract class DataSource<Key, Value> {
public static int COUNT_UNDEFINED = -1;
/**
- * Number of items that this DataSource can provide in total, or {@link #COUNT_UNDEFINED}.
- *
- * @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();
-
- /**
* Returns true if the data source guaranteed to produce a contiguous set of items,
* never producing gaps.
*/
@@ -111,7 +103,7 @@ public abstract class DataSource<Key, Value> {
*/
@AnyThread
@SuppressWarnings("WeakerAccess")
- public void addInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) {
+ public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
mOnInvalidatedCallbacks.add(onInvalidatedCallback);
}
@@ -122,7 +114,7 @@ public abstract class DataSource<Key, Value> {
*/
@AnyThread
@SuppressWarnings("WeakerAccess")
- public void removeInvalidatedCallback(InvalidatedCallback onInvalidatedCallback) {
+ public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
mOnInvalidatedCallbacks.remove(onInvalidatedCallback);
}
diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java
index 8cf6829c..0d452946 100644
--- a/android/arch/paging/KeyedDataSource.java
+++ b/android/arch/paging/KeyedDataSource.java
@@ -103,10 +103,6 @@ import java.util.List;
* @param <Value> Type of items being loaded by the DataSource.
*/
public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
- @Override
- public final int countItems() {
- return 0; // method not called, can't be overridden
- }
@Nullable
@Override
@@ -118,7 +114,14 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K
@Override
List<Value> loadBeforeImpl(
int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize) {
- return loadBefore(getKey(currentBeginItem), pageSize);
+ List<Value> list = loadBefore(getKey(currentBeginItem), pageSize);
+
+ if (list != null && list.size() > 1) {
+ // TODO: move out of keyed entirely, into the DB DataSource.
+ list = new ArrayList<>(list);
+ Collections.reverse(list);
+ }
+ return list;
}
@Nullable
@@ -191,6 +194,8 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @WorkerThread
+ @Override
public NullPaddedList<Value> loadInitial(
@Nullable Key key, int initialLoadSize, boolean enablePlaceholders) {
if (isInvalid()) {
diff --git a/android/arch/paging/LivePagedListProvider.java b/android/arch/paging/LivePagedListProvider.java
index b7c68dd6..07dd84bf 100644
--- a/android/arch/paging/LivePagedListProvider.java
+++ b/android/arch/paging/LivePagedListProvider.java
@@ -16,5 +16,133 @@
package android.arch.paging;
-abstract public class LivePagedListProvider<K, T> {
-} \ No newline at end of file
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.lifecycle.ComputableLiveData;
+import android.arch.lifecycle.LiveData;
+import android.support.annotation.AnyThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+
+/**
+ * Provides a {@code LiveData<PagedList>}, given a means to construct a DataSource.
+ * <p>
+ * Return type for data-loading system of an application or library to produce a
+ * {@code LiveData<PagedList>}, while leaving the details of the paging mechanism up to the
+ * consumer.
+ * <p>
+ * If you're using Room, it can generate a LivePagedListProvider from a query:
+ * <pre>
+ * {@literal @}Dao
+ * interface UserDao {
+ * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
+ * public abstract LivePagedListProvider&lt;Integer, User> usersByLastName();
+ * }</pre>
+ * In the above sample, {@code Integer} is used because it is the {@code Key} type of
+ * {@link TiledDataSource}. Currently, Room can only generate a {@code LIMIT}/{@code OFFSET},
+ * position based loader that uses TiledDataSource under the hood, and specifying {@code Integer}
+ * here lets you pass an initial loading position as an integer.
+ * <p>
+ * In the future, Room plans to offer other key types to support paging content with a
+ * {@link KeyedDataSource}.
+ *
+ * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if
+ * you're using TiledDataSource.
+ * @param <Value> Data type produced by the DataSource, and held by the PagedLists.
+ *
+ * @see PagedListAdapter
+ * @see DataSource
+ * @see PagedList
+ */
+public abstract class LivePagedListProvider<Key, Value> {
+
+ /**
+ * Construct a new data source to be wrapped in a new PagedList, which will be returned
+ * through the LiveData.
+ *
+ * @return The data source.
+ */
+ @WorkerThread
+ protected abstract DataSource<Key, Value> createDataSource();
+
+ /**
+ * Creates a LiveData of PagedLists, given the page size.
+ * <p>
+ * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
+ * {@link android.support.v7.widget.RecyclerView}.
+ *
+ * @param initialLoadKey Initial key used to load initial data from the data source.
+ * @param pageSize Page size defining how many items are loaded from a data source at a time.
+ * Recommended to be multiple times the size of item displayed at once.
+ *
+ * @return The LiveData of PagedLists.
+ */
+ @AnyThread
+ @NonNull
+ public LiveData<PagedList<Value>> create(@Nullable Key initialLoadKey, int pageSize) {
+ return create(initialLoadKey,
+ new PagedList.Config.Builder()
+ .setPageSize(pageSize)
+ .build());
+ }
+
+ /**
+ * Creates a LiveData of PagedLists, given the PagedList.Config.
+ * <p>
+ * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
+ * {@link android.support.v7.widget.RecyclerView}.
+ *
+ * @param initialLoadKey Initial key to pass to the data source to initialize data with.
+ * @param config PagedList.Config to use with created PagedLists. This specifies how the
+ * lists will load data.
+ *
+ * @return The LiveData of PagedLists.
+ */
+ @AnyThread
+ @NonNull
+ public LiveData<PagedList<Value>> create(@Nullable final Key initialLoadKey,
+ final PagedList.Config config) {
+ return new ComputableLiveData<PagedList<Value>>() {
+ @Nullable
+ private PagedList<Value> mList;
+ @Nullable
+ private DataSource<Key, Value> mDataSource;
+
+ private final DataSource.InvalidatedCallback mCallback =
+ new DataSource.InvalidatedCallback() {
+ @Override
+ public void onInvalidated() {
+ invalidate();
+ }
+ };
+
+ @Override
+ protected PagedList<Value> compute() {
+ @Nullable Key initializeKey = initialLoadKey;
+ if (mList != null) {
+ //noinspection unchecked
+ initializeKey = (Key) mList.getLastKey();
+ }
+
+ do {
+ if (mDataSource != null) {
+ mDataSource.removeInvalidatedCallback(mCallback);
+ }
+
+ mDataSource = createDataSource();
+ mDataSource.addInvalidatedCallback(mCallback);
+
+ mList = new PagedList.Builder<Key, Value>()
+ .setDataSource(mDataSource)
+ .setMainThreadExecutor(ArchTaskExecutor.getMainThreadExecutor())
+ .setBackgroundThreadExecutor(
+ ArchTaskExecutor.getIOThreadExecutor())
+ .setConfig(config)
+ .setInitialKey(initializeKey)
+ .build();
+ } while (mList.isDetached());
+ return mList;
+ }
+ }.getLiveData();
+ }
+}
diff --git a/android/arch/paging/NullPaddedList.java b/android/arch/paging/NullPaddedList.java
index 43000302..c7b0b231 100644
--- a/android/arch/paging/NullPaddedList.java
+++ b/android/arch/paging/NullPaddedList.java
@@ -16,11 +16,9 @@
package android.arch.paging;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
-import java.util.ArrayList;
+import java.util.AbstractList;
import java.util.List;
/**
@@ -31,18 +29,11 @@ import java.util.List;
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class NullPaddedList<Type> extends PagedList<Type> {
+public class NullPaddedList<Type> extends AbstractList<Type> {
List<Type> mList;
- int mTrailingNullCount;
- int mLeadingNullCount;
- int mPositionOffset;
-
- // track the items prepended/appended since the PagedList was initialized
- int mNumberPrepended;
- int mNumberAppended;
-
- NullPaddedList() {
- }
+ private int mTrailingNullCount;
+ private int mLeadingNullCount;
+ private int mPositionOffset;
@Override
public String toString() {
@@ -91,20 +82,6 @@ public class NullPaddedList<Type> extends PagedList<Type> {
mPositionOffset = positionOffset;
}
- /**
- * Create a copy of the passed NullPaddedList.
- *
- * @param other Other list to copy.
- */
- NullPaddedList(NullPaddedList<Type> other) {
- mLeadingNullCount = other.getLeadingNullCount();
- mList = other.isImmutable() ? other.mList : new ArrayList<>(other.mList);
- mTrailingNullCount = other.getTrailingNullCount();
-
- mNumberPrepended = other.getNumberPrepended();
- mNumberAppended = other.getNumberAppended();
- }
-
// --------------- PagedList API ---------------
@Override
@@ -124,46 +101,12 @@ public class NullPaddedList<Type> extends PagedList<Type> {
}
@Override
- public void loadAround(int index) {
- // do nothing - immutable, so no fetching will be done
- }
-
- @Override
public final int size() {
return getLoadedCount() + getLeadingNullCount() + getTrailingNullCount();
}
- public boolean isImmutable() {
- return true;
- }
-
- @Override
- public PagedList<Type> snapshot() {
- if (isImmutable()) {
- return this;
- }
- return new NullPaddedList<>(this);
- }
-
- @Override
- boolean isContiguous() {
- return true;
- }
-
- @Override
- public void addWeakCallback(@Nullable PagedList<Type> previousSnapshot,
- @NonNull Callback callback) {
- // no op, immutable
- }
-
- @Override
- public void removeWeakCallback(Callback callback) {
- // no op, immutable
- }
-
// --------------- Contiguous API ---------------
- @Override
public int getPositionOffset() {
return mPositionOffset;
}
@@ -194,12 +137,4 @@ public class NullPaddedList<Type> extends PagedList<Type> {
public int getTrailingNullCount() {
return mTrailingNullCount;
}
-
- int getNumberPrepended() {
- return mNumberPrepended;
- }
-
- int getNumberAppended() {
- return mNumberAppended;
- }
}
diff --git a/android/arch/paging/Page.java b/android/arch/paging/Page.java
new file mode 100644
index 00000000..e9890ed4
--- /dev/null
+++ b/android/arch/paging/Page.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.paging;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Immutable class representing a page of data loaded from a DataSource.
+ * <p>
+ * Optionally stores before/after keys for cases where they cannot be computed, but the DataSource
+ * can provide them as part of loading a page.
+ * <p>
+ * A page's list must never be modified.
+ */
+class Page<K, V> {
+ @SuppressWarnings("WeakerAccess")
+ @Nullable
+ public final K beforeKey;
+ @NonNull
+ public final List<V> items;
+ @SuppressWarnings("WeakerAccess")
+ @Nullable
+ public K afterKey;
+
+ Page(@NonNull List<V> items) {
+ this(null, items, null);
+ }
+
+ Page(@Nullable K beforeKey, @NonNull List<V> items, @Nullable K afterKey) {
+ this.beforeKey = beforeKey;
+ this.items = items;
+ this.afterKey = afterKey;
+ }
+}
diff --git a/android/arch/paging/PageArrayList.java b/android/arch/paging/PageArrayList.java
deleted file mode 100644
index b90d055a..00000000
--- a/android/arch/paging/PageArrayList.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class PageArrayList<T> extends PagedList<T> {
- // partial list of pages, doesn't include pages below the lowest accessed, or above the highest
- final ArrayList<List<T>> mPages;
-
- // to access page at index N, do mPages.get(N - mPageIndexOffset), but do bounds checking first!
- int mPageIndexOffset;
-
- final int mPageSize;
- final int mCount;
- final int mMaxPageCount;
-
- PageArrayList(int pageSize, int count) {
- mPages = new ArrayList<>();
- mPageSize = pageSize;
- mCount = count;
- mMaxPageCount = (mCount + mPageSize - 1) / mPageSize;
- }
-
- private PageArrayList(PageArrayList<T> other) {
- mPages = other.isImmutable() ? other.mPages : new ArrayList<>(other.mPages);
- mPageIndexOffset = other.mPageIndexOffset;
- mPageSize = other.mPageSize;
- mCount = other.size();
- mMaxPageCount = other.mMaxPageCount;
- }
-
- @Override
- public T get(int index) {
- if (index < 0 || index >= mCount) {
- throw new IllegalArgumentException();
- }
-
- int localPageIndex = getLocalPageIndex(index);
-
- List<T> page = getPage(localPageIndex);
-
- if (page == null) {
- // page empty
- return null;
- }
-
- return page.get(index % mPageSize);
- }
-
- @Nullable
- private List<T> getPage(int localPageIndex) {
- if (localPageIndex < 0 || localPageIndex >= mPages.size()) {
- // page not present
- return null;
- }
-
- return mPages.get(localPageIndex);
- }
-
- private int getLocalPageIndex(int index) {
- return index / mPageSize - mPageIndexOffset;
- }
-
- @Override
- public void loadAround(int index) {
- // do nothing - immutable, so no fetching will be done
- }
-
- @Override
- public int size() {
- return mCount;
- }
-
- @Override
- public boolean isImmutable() {
- return true;
- }
-
- boolean hasPage(int pageIndex) {
- final int localPageIndex = pageIndex - mPageIndexOffset;
- List<T> page = getPage(localPageIndex);
- return page != null && page.size() != 0;
- }
-
- @Override
- public PagedList<T> snapshot() {
- if (isImmutable()) {
- return this;
- }
- return new PageArrayList<>(this);
- }
-
- @Override
- boolean isContiguous() {
- return false;
- }
-
- @Override
- public void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
- @NonNull Callback callback) {
- // no op, immutable
- }
-
- @Override
- public void removeWeakCallback(Callback callback) {
- // no op, immutable
- }
-}
diff --git a/android/arch/paging/PageArrayListTest.java b/android/arch/paging/PageArrayListTest.java
deleted file mode 100644
index 135e640d..00000000
--- a/android/arch/paging/PageArrayListTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class PageArrayListTest {
- @Test
- public void simple() {
- List<String> data = Arrays.asList("A", "B", "C", "D", "E", "F");
- PageArrayList<String> list = new PageArrayList<>(2, data.size());
-
- assertEquals(2, list.mPageSize);
- assertEquals(data.size(), list.size());
- assertEquals(3, list.mMaxPageCount);
-
- for (int i = 0; i < data.size(); i++) {
- assertEquals(null, list.get(i));
- }
- for (int i = 0; i < data.size(); i += list.mPageSize) {
- list.mPages.add(data.subList(i, i + 2));
- }
- for (int i = 0; i < data.size(); i++) {
- assertEquals(data.get(i), list.get(i));
- }
- }
-}
diff --git a/android/arch/paging/PageResult.java b/android/arch/paging/PageResult.java
new file mode 100644
index 00000000..a4090f61
--- /dev/null
+++ b/android/arch/paging/PageResult.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.arch.paging;
+
+import android.support.annotation.AnyThread;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+
+class PageResult<K, V> {
+ static final int INIT = 0;
+
+ // contiguous results
+ static final int APPEND = 1;
+ static final int PREPEND = 2;
+
+ // non-contiguous, tile result
+ static final int TILE = 3;
+
+ public final int type;
+ public final Page<K, V> page;
+ @SuppressWarnings("WeakerAccess")
+ public final int leadingNulls;
+ @SuppressWarnings("WeakerAccess")
+ public final int trailingNulls;
+ @SuppressWarnings("WeakerAccess")
+ public final int positionOffset;
+
+ PageResult(int type, Page<K, V> page, int leadingNulls, int trailingNulls, int positionOffset) {
+ this.type = type;
+ this.page = page;
+ this.leadingNulls = leadingNulls;
+ this.trailingNulls = trailingNulls;
+ this.positionOffset = positionOffset;
+ }
+
+ interface Receiver<K, V> {
+ @AnyThread
+ void postOnPageResult(@NonNull PageResult<K, V> pageResult);
+ @MainThread
+ void onPageResult(@NonNull PageResult<K, V> pageResult);
+ }
+}
diff --git a/android/arch/paging/PagedList.java b/android/arch/paging/PagedList.java
index 6a31b689..1f07bfab 100644
--- a/android/arch/paging/PagedList.java
+++ b/android/arch/paging/PagedList.java
@@ -20,9 +20,12 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
+import java.lang.ref.WeakReference;
import java.util.AbstractList;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Lazy loading list that pages in content from a {@link DataSource}.
@@ -90,9 +93,28 @@ import java.util.concurrent.Executor;
* @param <T> The type of the entries in the list.
*/
public abstract class PagedList<T> extends AbstractList<T> {
- // Since we currently rely on implementation details of two implementations,
- // prevent external subclassing
- PagedList() {
+ final Executor mMainThreadExecutor;
+ final Executor mBackgroundThreadExecutor;
+ final Config mConfig;
+
+ @NonNull
+ final PagedStorage<?, T> mStorage;
+
+ int mLastLoad = 0;
+ T mLastItem = null;
+
+ private final AtomicBoolean mDetached = new AtomicBoolean(false);
+
+ protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+
+ PagedList(@NonNull PagedStorage<?, T> storage,
+ @NonNull Executor mainThreadExecutor,
+ @NonNull Executor backgroundThreadExecutor,
+ @NonNull Config config) {
+ mStorage = storage;
+ mMainThreadExecutor = mainThreadExecutor;
+ mBackgroundThreadExecutor = backgroundThreadExecutor;
+ mConfig = config;
}
/**
@@ -280,7 +302,13 @@ public abstract class PagedList<T> extends AbstractList<T> {
*/
@Override
@Nullable
- public abstract T get(int index);
+ public T get(int index) {
+ T item = mStorage.get(index);
+ if (item != null) {
+ mLastItem = item;
+ }
+ return item;
+ }
/**
@@ -288,7 +316,10 @@ public abstract class PagedList<T> extends AbstractList<T> {
*
* @param index Index at which to load.
*/
- public abstract void loadAround(int index);
+ public void loadAround(int index) {
+ mLastLoad = index + getPositionOffset();
+ loadAroundInternal(index);
+ }
/**
@@ -297,7 +328,9 @@ public abstract class PagedList<T> extends AbstractList<T> {
* @return Current total size of the list.
*/
@Override
- public abstract int size();
+ public int size() {
+ return mStorage.size();
+ }
/**
* Returns whether the list is immutable. Immutable lists may not become mutable again, and may
@@ -305,15 +338,25 @@ public abstract class PagedList<T> extends AbstractList<T> {
*
* @return True if the PagedList is immutable.
*/
- public abstract boolean isImmutable();
+ @SuppressWarnings("WeakerAccess")
+ public boolean isImmutable() {
+ return isDetached();
+ }
/**
* Returns an immutable snapshot of the PagedList. If this PagedList is already
* immutable, it will be returned.
*
- * @return Immutable snapshot of PagedList, which may be the PagedList itself.
+ * @return Immutable snapshot of PagedList data.
*/
- public abstract List<T> snapshot();
+ @NonNull
+ public List<T> snapshot() {
+ if (isImmutable()) {
+ return this;
+ }
+
+ return new SnapshotPagedList<>(this);
+ }
abstract boolean isContiguous();
@@ -328,9 +371,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
* @return Key of position most recently passed to {@link #loadAround(int)}.
*/
@Nullable
- public Object getLastKey() {
- return null;
- }
+ public abstract Object getLastKey();
/**
* True if the PagedList has detached the DataSource it was loading from, and will no longer
@@ -338,8 +379,9 @@ public abstract class PagedList<T> extends AbstractList<T> {
*
* @return True if the data source is detached.
*/
+ @SuppressWarnings("WeakerAccess")
public boolean isDetached() {
- return true;
+ return mDetached.get();
}
/**
@@ -349,7 +391,9 @@ public abstract class PagedList<T> extends AbstractList<T> {
* signal to stop loading. The PagedList will continue to present existing data, but will not
* initiate new loads.
*/
+ @SuppressWarnings("WeakerAccess")
public void detach() {
+ mDetached.set(true);
}
/**
@@ -361,7 +405,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
* If the DataSource is a {@link KeyedDataSource}, and thus doesn't use positions, returns 0.
*/
public int getPositionOffset() {
- return 0;
+ return mStorage.getPositionOffset();
}
/**
@@ -385,16 +429,68 @@ public abstract class PagedList<T> extends AbstractList<T> {
* @param callback Callback to dispatch to.
* @see #removeWeakCallback(Callback)
*/
- public abstract void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
- @NonNull Callback callback);
+ @SuppressWarnings("WeakerAccess")
+ public void addWeakCallback(@Nullable List<T> previousSnapshot, @NonNull Callback callback) {
+ if (previousSnapshot != null && previousSnapshot != this) {
+ PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot;
+ //noinspection unchecked
+ dispatchUpdatesSinceSnapshot(storageSnapshot, callback);
+ }
+
+ // first, clean up any empty weak refs
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ Callback currentCallback = mCallbacks.get(i).get();
+ if (currentCallback == null) {
+ mCallbacks.remove(i);
+ }
+ }
+ // then add the new one
+ mCallbacks.add(new WeakReference<>(callback));
+ }
/**
* Removes a previously added callback.
*
* @param callback Callback, previously added.
- * @see #addWeakCallback(PagedList, Callback)
+ * @see #addWeakCallback(List, Callback)
*/
- public abstract void removeWeakCallback(Callback callback);
+ @SuppressWarnings("WeakerAccess")
+ public void removeWeakCallback(@NonNull Callback callback) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ Callback currentCallback = mCallbacks.get(i).get();
+ if (currentCallback == null || currentCallback == callback) {
+ // found callback, or empty weak ref
+ mCallbacks.remove(i);
+ }
+ }
+ }
+
+ void notifyInserted(int position, int count) {
+ if (count != 0) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ Callback callback = mCallbacks.get(i).get();
+ if (callback != null) {
+ callback.onInserted(position, count);
+ }
+ }
+ }
+ }
+
+ void notifyChanged(int position, int count) {
+ if (count != 0) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ Callback callback = mCallbacks.get(i).get();
+ if (callback != null) {
+ callback.onChanged(position, count);
+ }
+ }
+ }
+ }
+
+ abstract void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> snapshot,
+ @NonNull Callback callback);
+
+ abstract void loadAroundInternal(int index);
/**
* Callback signaling when content is loaded into the list.
@@ -545,10 +641,15 @@ public abstract class PagedList<T> extends AbstractList<T> {
* Defines how many items to load when first load occurs, if you are using a
* {@link KeyedDataSource}.
* <p>
- * If you are using an {@link TiledDataSource}, this value is currently ignored.
- * Otherwise, this value will be passed to
- * {@link KeyedDataSource#loadInitial(int)} to load a (typically) larger amount
- * of data on first load.
+ * This value is typically larger than page size, so on first load data there's a large
+ * enough range of content loaded to cover small scrolls.
+ * <p>
+ * If used with a {@link TiledDataSource}, this value is rounded to the nearest number
+ * of pages, with a minimum of two pages, and loaded with a single call to
+ * {@link TiledDataSource#loadRange(int, int)}.
+ * <p>
+ * If used with a {@link KeyedDataSource}, this value will be passed to
+ * {@link KeyedDataSource#loadInitial(int)}.
* <p>
* If not set, defaults to three times page size.
*
diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java
index c7b61d9f..abcff415 100644
--- a/android/arch/paging/PagedListAdapterHelper.java
+++ b/android/arch/paging/PagedListAdapterHelper.java
@@ -25,8 +25,6 @@ import android.support.v7.util.DiffUtil;
import android.support.v7.util.ListUpdateCallback;
import android.support.v7.widget.RecyclerView;
-import java.util.List;
-
/**
* Helper object for mapping a {@link PagedList} into a
* {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}.
@@ -120,15 +118,15 @@ import java.util.List;
* @param <T> Type of the PagedLists this helper will receive.
*/
public class PagedListAdapterHelper<T> {
+ // updateCallback notifications must only be notified *after* new data and item count are stored
+ // this ensures Adapter#notifyItemRangeInserted etc are accessing the new data
private final ListUpdateCallback mUpdateCallback;
private final ListAdapterConfig<T> mConfig;
- // true if our listener is detached from mList, because it's been snapshotted
- private boolean mUpdateScheduled;
-
private boolean mIsContiguous;
- private PagedList<T> mList;
+ private PagedList<T> mPagedList;
+ private PagedList<T> mSnapshot;
// Max generation of currently scheduled runnable
private int mMaxScheduledGeneration;
@@ -182,12 +180,17 @@ public class PagedListAdapterHelper<T> {
@SuppressWarnings("WeakerAccess")
@Nullable
public T getItem(int index) {
- if (mList == null) {
- throw new IndexOutOfBoundsException("Item count is zero, getItem() call is invalid");
+ if (mPagedList == null) {
+ if (mSnapshot == null) {
+ throw new IndexOutOfBoundsException(
+ "Item count is zero, getItem() call is invalid");
+ } else {
+ return mSnapshot.get(index);
+ }
}
- mList.loadAround(index);
- return mList.get(index);
+ mPagedList.loadAround(index);
+ return mPagedList.get(index);
}
/**
@@ -198,7 +201,11 @@ public class PagedListAdapterHelper<T> {
*/
@SuppressWarnings("WeakerAccess")
public int getItemCount() {
- return mList == null ? 0 : mList.size();
+ if (mPagedList != null) {
+ return mPagedList.size();
+ }
+
+ return mSnapshot == null ? 0 : mSnapshot.size();
}
/**
@@ -212,7 +219,7 @@ public class PagedListAdapterHelper<T> {
*/
public void setList(final PagedList<T> pagedList) {
if (pagedList != null) {
- if (mList == null) {
+ if (mPagedList == null && mSnapshot == null) {
mIsContiguous = pagedList.isContiguous();
} else {
if (pagedList.isContiguous() != mIsContiguous) {
@@ -222,7 +229,7 @@ public class PagedListAdapterHelper<T> {
}
}
- if (pagedList == mList) {
+ if (pagedList == mPagedList) {
// nothing to do
return;
}
@@ -231,49 +238,55 @@ public class PagedListAdapterHelper<T> {
final int runGeneration = ++mMaxScheduledGeneration;
if (pagedList == null) {
- mUpdateCallback.onRemoved(0, mList.size());
- mList.removeWeakCallback(mPagedListCallback);
- mList = null;
+ int removedCount = getItemCount();
+ if (mPagedList != null) {
+ mPagedList.removeWeakCallback(mPagedListCallback);
+ mPagedList = null;
+ } else if (mSnapshot != null) {
+ mSnapshot = null;
+ }
+ // dispatch update callback after updating mPagedList/mSnapshot
+ mUpdateCallback.onRemoved(0, removedCount);
return;
}
- if (mList == null) {
+ if (mPagedList == null && mSnapshot == null) {
// fast simple first insert
- mUpdateCallback.onInserted(0, pagedList.size());
- mList = pagedList;
+ mPagedList = pagedList;
pagedList.addWeakCallback(null, mPagedListCallback);
+
+ // dispatch update callback after updating mPagedList/mSnapshot
+ mUpdateCallback.onInserted(0, pagedList.size());
return;
}
- if (!mList.isImmutable()) {
+ if (mPagedList != null) {
// first update scheduled on this list, so capture mPages as a snapshot, removing
// callbacks so we don't have resolve updates against a moving target
- mList.removeWeakCallback(mPagedListCallback);
- mList = (PagedList<T>) mList.snapshot();
+ mPagedList.removeWeakCallback(mPagedListCallback);
+ mSnapshot = (PagedList<T>) mPagedList.snapshot();
+ mPagedList = null;
+ }
+
+ if (mSnapshot == null || mPagedList != null) {
+ throw new IllegalStateException("must be in snapshot state to diff");
}
- final PagedList<T> oldSnapshot = mList;
- final List<T> newSnapshot = pagedList.snapshot();
- mUpdateScheduled = true;
+ final PagedList<T> oldSnapshot = mSnapshot;
+ final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot();
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result;
- if (mIsContiguous) {
- result = ContiguousDiffHelper.computeDiff(
- (NullPaddedList<T>) oldSnapshot, (NullPaddedList<T>) newSnapshot,
- mConfig.getDiffCallback(), true);
- } else {
- result = SparseDiffHelper.computeDiff(
- (PageArrayList<T>) oldSnapshot, (PageArrayList<T>) newSnapshot,
- mConfig.getDiffCallback(), true);
- }
+ result = PagedStorageDiffHelper.computeDiff(
+ oldSnapshot.mStorage,
+ newSnapshot.mStorage,
+ mConfig.getDiffCallback());
mConfig.getMainThreadExecutor().execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {
- mUpdateScheduled = false;
latchPagedList(pagedList, newSnapshot, result);
}
}
@@ -283,16 +296,21 @@ public class PagedListAdapterHelper<T> {
}
private void latchPagedList(
- PagedList<T> newList, List<T> diffSnapshot,
+ PagedList<T> newList, PagedList<T> diffSnapshot,
DiffUtil.DiffResult diffResult) {
- if (mIsContiguous) {
- ContiguousDiffHelper.dispatchDiff(mUpdateCallback,
- (NullPaddedList<T>) mList, (ContiguousPagedList<T>) newList, diffResult);
- } else {
- SparseDiffHelper.dispatchDiff(mUpdateCallback, diffResult);
+ if (mSnapshot == null || mPagedList != null) {
+ throw new IllegalStateException("must be in snapshot state to apply diff");
}
- mList = newList;
- newList.addWeakCallback((PagedList<T>) diffSnapshot, mPagedListCallback);
+
+ PagedList<T> previousSnapshot = mSnapshot;
+ mPagedList = newList;
+ mSnapshot = null;
+
+ // dispatch update callback after updating mPagedList/mSnapshot
+ PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
+ previousSnapshot.mStorage, newList.mStorage, diffResult);
+
+ newList.addWeakCallback(diffSnapshot, mPagedListCallback);
}
/**
@@ -307,6 +325,9 @@ public class PagedListAdapterHelper<T> {
@SuppressWarnings("WeakerAccess")
@Nullable
public PagedList<T> getCurrentList() {
- return mList;
+ if (mSnapshot != null) {
+ return mSnapshot;
+ }
+ return mPagedList;
}
}
diff --git a/android/arch/paging/PagedListAdapterHelperTest.java b/android/arch/paging/PagedListAdapterHelperTest.java
index 3518540c..963d0479 100644
--- a/android/arch/paging/PagedListAdapterHelperTest.java
+++ b/android/arch/paging/PagedListAdapterHelperTest.java
@@ -21,6 +21,7 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -289,6 +290,64 @@ public class PagedListAdapterHelperTest {
assertFalse(helper.getCurrentList().isImmutable());
}
+ @Test
+ public void itemCountUpdatedBeforeListUpdateCallbacks() {
+ // verify that itemCount is updated in the helper before dispatching ListUpdateCallbacks
+
+ final int[] expectedCount = new int[] { 0 };
+ // provides access to helper, which must be constructed after callback
+ final PagedListAdapterHelper[] helperAccessor = new PagedListAdapterHelper[] { null };
+
+ ListUpdateCallback callback = new ListUpdateCallback() {
+ @Override
+ public void onInserted(int position, int count) {
+ assertEquals(expectedCount[0], helperAccessor[0].getItemCount());
+ }
+
+ @Override
+ public void onRemoved(int position, int count) {
+ assertEquals(expectedCount[0], helperAccessor[0].getItemCount());
+ }
+
+ @Override
+ public void onMoved(int fromPosition, int toPosition) {
+ fail("not expected");
+ }
+
+ @Override
+ public void onChanged(int position, int count, Object payload) {
+ fail("not expected");
+ }
+ };
+
+ PagedListAdapterHelper<String> helper = createHelper(callback, STRING_DIFF_CALLBACK);
+ helperAccessor[0] = helper;
+
+ PagedList.Config config = new PagedList.Config.Builder()
+ .setPageSize(20)
+ .build();
+
+
+ // in the fast-add case...
+ expectedCount[0] = 5;
+ assertEquals(0, helper.getItemCount());
+ helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 5), 0));
+ assertEquals(5, helper.getItemCount());
+
+ // in the slow, diff on BG thread case...
+ expectedCount[0] = 10;
+ assertEquals(5, helper.getItemCount());
+ helper.setList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 10), 0));
+ drain();
+ assertEquals(10, helper.getItemCount());
+
+ // and in the fast-remove case
+ expectedCount[0] = 0;
+ assertEquals(10, helper.getItemCount());
+ helper.setList(null);
+ assertEquals(0, helper.getItemCount());
+ }
+
private void drainExceptDiffThread() {
boolean executed;
do {
diff --git a/android/arch/paging/PagedStorage.java b/android/arch/paging/PagedStorage.java
new file mode 100644
index 00000000..7f91290d
--- /dev/null
+++ b/android/arch/paging/PagedStorage.java
@@ -0,0 +1,433 @@
+/*
+ * 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.paging;
+
+import android.support.annotation.NonNull;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+final class PagedStorage<K, V> extends AbstractList<V> {
+ // Always set
+ private int mLeadingNullCount;
+ /**
+ * List of pages in storage.
+ *
+ * Two storage modes:
+ *
+ * Contiguous - all content in mPages is valid and loaded, but may return false from isTiled().
+ * Safe to access any item in any page.
+ *
+ * Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true.
+ * mPages may have nulls, or placeholder (empty) pages while content is loading.
+ */
+ private final ArrayList<Page<K, V>> mPages;
+ private int mTrailingNullCount;
+
+ private int mPositionOffset;
+ /**
+ * Number of items represented by {@link #mPages}. If tiling is enabled, unloaded items in
+ * {@link #mPages} may be null, but this value still counts them.
+ */
+ private int mStorageCount;
+
+ // If mPageSize > 0, tiling is enabled, 'mPages' may have gaps, and leadingPages is set
+ private int mPageSize;
+
+ private int mNumberPrepended;
+ private int mNumberAppended;
+
+ // only used in tiling case
+ private Page<K, V> mPlaceholderPage;
+
+ PagedStorage() {
+ mLeadingNullCount = 0;
+ mPages = new ArrayList<>();
+ mTrailingNullCount = 0;
+ mPositionOffset = 0;
+ mStorageCount = 0;
+ mPageSize = 1;
+ mNumberPrepended = 0;
+ mNumberAppended = 0;
+ }
+
+ PagedStorage(int leadingNulls, Page<K, V> page, int trailingNulls) {
+ this();
+ init(leadingNulls, page, trailingNulls, 0);
+ }
+
+ private PagedStorage(PagedStorage<K, V> other) {
+ mLeadingNullCount = other.mLeadingNullCount;
+ mPages = new ArrayList<>(other.mPages);
+ mTrailingNullCount = other.mTrailingNullCount;
+ mPositionOffset = other.mPositionOffset;
+ mStorageCount = other.mStorageCount;
+ mPageSize = other.mPageSize;
+ mNumberPrepended = other.mNumberPrepended;
+ mNumberAppended = other.mNumberAppended;
+
+ // preserve placeholder page so we can locate placeholder pages if needed later
+ mPlaceholderPage = other.mPlaceholderPage;
+ }
+
+ PagedStorage<K, V> snapshot() {
+ return new PagedStorage<>(this);
+ }
+
+ private void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset) {
+ mLeadingNullCount = leadingNulls;
+ mPages.clear();
+ mPages.add(page);
+ mTrailingNullCount = trailingNulls;
+
+ mPositionOffset = positionOffset;
+ mStorageCount = page.items.size();
+
+ // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled
+ // even if it will break if nulls convert.
+ mPageSize = page.items.size();
+
+ mNumberPrepended = 0;
+ mNumberAppended = 0;
+ }
+
+ void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset,
+ @NonNull Callback callback) {
+ init(leadingNulls, page, trailingNulls, positionOffset);
+ callback.onInitialized(size());
+ }
+
+ @Override
+ public V get(int i) {
+ if (i < 0 || i >= size()) {
+ throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size());
+ }
+
+ // is it definitely outside 'mPages'?
+ int localIndex = i - mLeadingNullCount;
+ if (localIndex < 0 || localIndex >= mStorageCount) {
+ return null;
+ }
+
+ int localPageIndex;
+ int pageInternalIndex;
+
+ if (isTiled()) {
+ // it's inside mPages, and we're tiled. Jump to correct tile.
+ localPageIndex = localIndex / mPageSize;
+ pageInternalIndex = localIndex % mPageSize;
+ } else {
+ // it's inside mPages, but page sizes aren't regular. Walk to correct tile.
+ // Pages can only be null while tiled, so accessing page count is safe.
+ pageInternalIndex = localIndex;
+ final int localPageCount = mPages.size();
+ for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) {
+ int pageSize = mPages.get(localPageIndex).items.size();
+ if (pageSize > pageInternalIndex) {
+ // stop, found the page
+ break;
+ }
+ pageInternalIndex -= pageSize;
+ }
+ }
+
+ Page<?, V> page = mPages.get(localPageIndex);
+ if (page == null || page.items.size() == 0) {
+ // can only occur in tiled case, with untouched inner/placeholder pages
+ return null;
+ }
+ return page.items.get(pageInternalIndex);
+ }
+
+ /**
+ * Returns true if all pages are the same size, except for the last, which may be smaller
+ */
+ boolean isTiled() {
+ return mPageSize > 0;
+ }
+
+ int getLeadingNullCount() {
+ return mLeadingNullCount;
+ }
+
+ int getTrailingNullCount() {
+ return mTrailingNullCount;
+ }
+
+ int getStorageCount() {
+ return mStorageCount;
+ }
+
+ int getNumberAppended() {
+ return mNumberAppended;
+ }
+
+ int getNumberPrepended() {
+ return mNumberPrepended;
+ }
+
+ int getPageCount() {
+ return mPages.size();
+ }
+
+ interface Callback {
+ void onInitialized(int count);
+ void onPagePrepended(int leadingNulls, int changed, int added);
+ void onPageAppended(int endPosition, int changed, int added);
+ void onPagePlaceholderInserted(int pageIndex);
+ void onPageInserted(int start, int count);
+ }
+
+ int getPositionOffset() {
+ return mPositionOffset;
+ }
+
+ @Override
+ public int size() {
+ return mLeadingNullCount + mStorageCount + mTrailingNullCount;
+ }
+
+ int computeLeadingNulls() {
+ int total = mLeadingNullCount;
+ final int pageCount = mPages.size();
+ for (int i = 0; i < pageCount; i++) {
+ Page page = mPages.get(i);
+ if (page != null && page != mPlaceholderPage) {
+ break;
+ }
+ total += mPageSize;
+ }
+ return total;
+ }
+
+ int computeTrailingNulls() {
+ int total = mTrailingNullCount;
+ for (int i = mPages.size() - 1; i >= 0; i--) {
+ Page page = mPages.get(i);
+ if (page != null && page != mPlaceholderPage) {
+ break;
+ }
+ total += mPageSize;
+ }
+ return total;
+ }
+
+ // ---------------- Contiguous API -------------------
+
+ V getFirstContiguousItem() {
+ // safe to access first page's first item here:
+ // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
+ return mPages.get(0).items.get(0);
+ }
+
+ V getLastContiguousItem() {
+ // safe to access last page's last item here:
+ // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
+ Page<K, V> page = mPages.get(mPages.size() - 1);
+ return page.items.get(page.items.size() - 1);
+ }
+
+ public void prependPage(@NonNull Page<K, V> page, @NonNull Callback callback) {
+ final int count = page.items.size();
+ if (count == 0) {
+ // Nothing returned from source, stop loading in this direction
+ return;
+ }
+ if (mPageSize > 0 && count != mPageSize) {
+ if (mPages.size() == 1 && count > mPageSize) {
+ // prepending to a single item - update current page size to that of 'inner' page
+ mPageSize = count;
+ } else {
+ // no longer tiled
+ mPageSize = -1;
+ }
+ }
+
+ mPages.add(0, page);
+ mStorageCount += count;
+
+ final int changedCount = Math.min(mLeadingNullCount, count);
+ final int addedCount = count - changedCount;
+
+ if (changedCount != 0) {
+ mLeadingNullCount -= changedCount;
+ }
+ mPositionOffset -= addedCount;
+ mNumberPrepended += count;
+
+ callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount);
+ }
+
+ public void appendPage(@NonNull Page<K, V> page, @NonNull Callback callback) {
+ final int count = page.items.size();
+ if (count == 0) {
+ // Nothing returned from source, stop loading in this direction
+ return;
+ }
+
+ if (mPageSize > 0) {
+ // if the previous page was smaller than mPageSize,
+ // or if this page is larger than the previous, disable tiling
+ if (mPages.get(mPages.size() - 1).items.size() != mPageSize
+ || count > mPageSize) {
+ mPageSize = -1;
+ }
+ }
+
+ mPages.add(page);
+ mStorageCount += count;
+
+ final int changedCount = Math.min(mTrailingNullCount, count);
+ final int addedCount = count - changedCount;
+
+ if (changedCount != 0) {
+ mTrailingNullCount -= changedCount;
+ }
+ mNumberAppended += count;
+ callback.onPageAppended(mLeadingNullCount + mStorageCount - count,
+ changedCount, addedCount);
+ }
+
+ // ------------------ Non-Contiguous API (tiling required) ----------------------
+
+ public void insertPage(int position, @NonNull Page<K, V> page, Callback callback) {
+ final int newPageSize = page.items.size();
+ if (newPageSize != mPageSize) {
+ // differing page size is OK in 2 cases, when the page is being added:
+ // 1) to the end (in which case, ignore new smaller size)
+ // 2) only the last page has been added so far (in which case, adopt new bigger size)
+
+ int size = size();
+ boolean addingLastPage = position == (size - size % mPageSize)
+ && newPageSize < mPageSize;
+ boolean onlyEndPagePresent = mTrailingNullCount == 0 && mPages.size() == 1
+ && newPageSize > mPageSize;
+
+ // OK only if existing single page, and it's the last one
+ if (!onlyEndPagePresent && !addingLastPage) {
+ throw new IllegalArgumentException("page introduces incorrect tiling");
+ }
+ if (onlyEndPagePresent) {
+ mPageSize = newPageSize;
+ }
+ }
+
+ int pageIndex = position / mPageSize;
+
+ allocatePageRange(pageIndex, pageIndex);
+
+ int localPageIndex = pageIndex - mLeadingNullCount / mPageSize;
+
+ Page<K, V> oldPage = mPages.get(localPageIndex);
+ if (oldPage != null && oldPage != mPlaceholderPage) {
+ throw new IllegalArgumentException(
+ "Invalid position " + position + ": data already loaded");
+ }
+ mPages.set(localPageIndex, page);
+ callback.onPageInserted(position, page.items.size());
+ }
+
+ private Page<K, V> getPlaceholderPage() {
+ if (mPlaceholderPage == null) {
+ @SuppressWarnings("unchecked")
+ List<V> list = Collections.emptyList();
+ mPlaceholderPage = new Page<>(null, list, null);
+ }
+ return mPlaceholderPage;
+ }
+
+ private void allocatePageRange(final int minimumPage, final int maximumPage) {
+ int leadingNullPages = mLeadingNullCount / mPageSize;
+
+ if (minimumPage < leadingNullPages) {
+ for (int i = 0; i < leadingNullPages - minimumPage; i++) {
+ mPages.add(0, null);
+ }
+ int newStorageAllocated = (leadingNullPages - minimumPage) * mPageSize;
+ mStorageCount += newStorageAllocated;
+ mLeadingNullCount -= newStorageAllocated;
+
+ leadingNullPages = minimumPage;
+ }
+ if (maximumPage >= leadingNullPages + mPages.size()) {
+ int newStorageAllocated = Math.min(mTrailingNullCount,
+ (maximumPage + 1 - (leadingNullPages + mPages.size())) * mPageSize);
+ for (int i = mPages.size(); i <= maximumPage - leadingNullPages; i++) {
+ mPages.add(mPages.size(), null);
+ }
+ mStorageCount += newStorageAllocated;
+ mTrailingNullCount -= newStorageAllocated;
+ }
+ }
+
+ public void allocatePlaceholders(int index, int prefetchDistance,
+ int pageSize, Callback callback) {
+ if (pageSize != mPageSize) {
+ if (pageSize < mPageSize) {
+ throw new IllegalArgumentException("Page size cannot be reduced");
+ }
+ if (mPages.size() != 1 || mTrailingNullCount != 0) {
+ // not in single, last page allocated case - can't change page size
+ throw new IllegalArgumentException(
+ "Page size can change only if last page is only one present");
+ }
+ mPageSize = pageSize;
+ }
+
+ final int maxPageCount = (size() + mPageSize - 1) / mPageSize;
+ int minimumPage = Math.max((index - prefetchDistance) / mPageSize, 0);
+ int maximumPage = Math.min((index + prefetchDistance) / mPageSize, maxPageCount - 1);
+
+ allocatePageRange(minimumPage, maximumPage);
+ int leadingNullPages = mLeadingNullCount / mPageSize;
+ for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) {
+ int localPageIndex = pageIndex - leadingNullPages;
+ if (mPages.get(localPageIndex) == null) {
+ mPages.set(localPageIndex, getPlaceholderPage());
+ callback.onPagePlaceholderInserted(pageIndex);
+ }
+ }
+ }
+
+ public boolean hasPage(int pageSize, int index) {
+ // NOTE: we pass pageSize here to avoid in case mPageSize
+ // not fully initialized (when last page only one loaded)
+ int leadingNullPages = mLeadingNullCount / pageSize;
+
+ if (index < leadingNullPages || index >= leadingNullPages + mPages.size()) {
+ return false;
+ }
+
+ Page<K, V> page = mPages.get(index - leadingNullPages);
+
+ return page != null && page != mPlaceholderPage;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder("leading " + mLeadingNullCount
+ + ", storage " + mStorageCount
+ + ", trailing " + getTrailingNullCount());
+
+ for (int i = 0; i < mPages.size(); i++) {
+ ret.append(" ").append(mPages.get(i));
+ }
+ return ret.toString();
+ }
+}
diff --git a/android/arch/paging/ContiguousDiffHelper.java b/android/arch/paging/PagedStorageDiffHelper.java
index 7dd194b2..6fc70390 100644
--- a/android/arch/paging/ContiguousDiffHelper.java
+++ b/android/arch/paging/PagedStorageDiffHelper.java
@@ -16,36 +16,31 @@
package android.arch.paging;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
import android.support.v7.recyclerview.extensions.DiffCallback;
import android.support.v7.util.DiffUtil;
import android.support.v7.util.ListUpdateCallback;
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class ContiguousDiffHelper {
- private ContiguousDiffHelper() {
+class PagedStorageDiffHelper {
+ private PagedStorageDiffHelper() {
}
- @NonNull
static <T> DiffUtil.DiffResult computeDiff(
- final NullPaddedList<T> oldList, final NullPaddedList<T> newList,
- final DiffCallback<T> diffCallback, boolean detectMoves) {
+ final PagedStorage<?, T> oldList,
+ final PagedStorage<?, T> newList,
+ final DiffCallback<T> diffCallback) {
+ final int oldOffset = oldList.computeLeadingNulls();
+ final int newOffset = newList.computeLeadingNulls();
+
+ final int oldSize = oldList.size() - oldOffset - oldList.computeTrailingNulls();
+ final int newSize = newList.size() - newOffset - newList.computeTrailingNulls();
- if (!oldList.isImmutable()) {
- throw new IllegalArgumentException("list must be immutable to safely perform diff");
- }
- if (!newList.isImmutable()) {
- throw new IllegalArgumentException("list must be immutable to safely perform diff");
- }
return DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.mList.get(oldItemPosition);
- T newItem = newList.mList.get(newItemPosition);
+ T oldItem = oldList.get(oldItemPosition + oldOffset);
+ T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
if (oldItem == null || newItem == null) {
return null;
}
@@ -54,21 +49,22 @@ class ContiguousDiffHelper {
@Override
public int getOldListSize() {
- return oldList.mList.size();
+ return oldSize;
}
@Override
public int getNewListSize() {
- return newList.mList.size();
+ return newSize;
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.mList.get(oldItemPosition);
- T newItem = newList.mList.get(newItemPosition);
+ T oldItem = oldList.get(oldItemPosition + oldOffset);
+ T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
if (oldItem == newItem) {
return true;
}
+ //noinspection SimplifiableIfStatement
if (oldItem == null || newItem == null) {
return false;
}
@@ -77,18 +73,19 @@ class ContiguousDiffHelper {
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.mList.get(oldItemPosition);
- T newItem = newList.mList.get(newItemPosition);
+ T oldItem = oldList.get(oldItemPosition + oldOffset);
+ T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
if (oldItem == newItem) {
return true;
}
+ //noinspection SimplifiableIfStatement
if (oldItem == null || newItem == null) {
return false;
}
return diffCallback.areContentsTheSame(oldItem, newItem);
}
- }, detectMoves);
+ }, true);
}
private static class OffsettingListUpdateCallback implements ListUpdateCallback {
@@ -134,21 +131,25 @@ class ContiguousDiffHelper {
* immediately after dispatching this diff.
*/
static <T> void dispatchDiff(ListUpdateCallback callback,
- final NullPaddedList<T> oldList, final NullPaddedList<T> newList,
+ final PagedStorage<?, T> oldList,
+ final PagedStorage<?, T> newList,
final DiffUtil.DiffResult diffResult) {
- if (oldList.getLeadingNullCount() == 0
- && oldList.getTrailingNullCount() == 0
- && newList.getLeadingNullCount() == 0
- && newList.getTrailingNullCount() == 0) {
+ final int trailingOld = oldList.computeTrailingNulls();
+ final int trailingNew = newList.computeTrailingNulls();
+ final int leadingOld = oldList.computeLeadingNulls();
+ final int leadingNew = newList.computeLeadingNulls();
+
+ if (trailingOld == 0
+ && trailingNew == 0
+ && leadingOld == 0
+ && leadingNew == 0) {
// Simple case, dispatch & return
diffResult.dispatchUpdatesTo(callback);
return;
}
// First, remove or insert trailing nulls
- final int trailingOld = oldList.getTrailingNullCount();
- final int trailingNew = newList.getTrailingNullCount();
if (trailingOld > trailingNew) {
int count = trailingOld - trailingNew;
callback.onRemoved(oldList.size() - count, count);
@@ -157,8 +158,6 @@ class ContiguousDiffHelper {
}
// Second, remove or insert leading nulls
- final int leadingOld = oldList.getLeadingNullCount();
- final int leadingNew = newList.getLeadingNullCount();
if (leadingOld > leadingNew) {
callback.onRemoved(0, leadingOld - leadingNew);
} else if (leadingOld < leadingNew) {
diff --git a/android/arch/paging/ContiguousDiffHelperTest.java b/android/arch/paging/PagedStorageDiffHelperTest.java
index 4f221b34..8cb92246 100644
--- a/android/arch/paging/ContiguousDiffHelperTest.java
+++ b/android/arch/paging/PagedStorageDiffHelperTest.java
@@ -16,6 +16,9 @@
package android.arch.paging;
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -29,11 +32,12 @@ import android.support.v7.util.ListUpdateCallback;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
+
+import java.util.Arrays;
@SmallTest
@RunWith(JUnit4.class)
-public class ContiguousDiffHelperTest {
+public class PagedStorageDiffHelperTest {
private interface CallbackValidator {
void validate(ListUpdateCallback callback);
}
@@ -51,13 +55,18 @@ public class ContiguousDiffHelperTest {
}
};
- private void validateTwoListDiff(StringPagedList oldList, StringPagedList newList,
+ public static Page<Integer, String> createPage(String... items) {
+ return new Page<>(Arrays.asList(items));
+ }
+
+ private static void validateTwoListDiff(PagedStorage<?, String> oldList,
+ PagedStorage<?, String> newList,
CallbackValidator callbackValidator) {
- DiffUtil.DiffResult diffResult = ContiguousDiffHelper.computeDiff(oldList, newList,
- DIFF_CALLBACK, false);
+ DiffUtil.DiffResult diffResult = PagedStorageDiffHelper.computeDiff(
+ oldList, newList, DIFF_CALLBACK);
- ListUpdateCallback listUpdateCallback = Mockito.mock(ListUpdateCallback.class);
- ContiguousDiffHelper.dispatchDiff(listUpdateCallback, oldList, newList, diffResult);
+ ListUpdateCallback listUpdateCallback = mock(ListUpdateCallback.class);
+ PagedStorageDiffHelper.dispatchDiff(listUpdateCallback, oldList, newList, diffResult);
callbackValidator.validate(listUpdateCallback);
}
@@ -65,8 +74,35 @@ public class ContiguousDiffHelperTest {
@Test
public void sameListNoUpdates() {
validateTwoListDiff(
- new StringPagedList(5, 5, "a", "b", "c"),
- new StringPagedList(5, 5, "a", "b", "c"),
+ new PagedStorage<>(5, createPage("a", "b", "c"), 5),
+ new PagedStorage<>(5, createPage("a", "b", "c"), 5),
+ new CallbackValidator() {
+ @Override
+ public void validate(ListUpdateCallback callback) {
+ verifyZeroInteractions(callback);
+ }
+ }
+ );
+ }
+
+ @Test
+ public void sameListNoUpdatesPlaceholder() {
+ PagedStorage<Integer, String> storageNoPlaceholder =
+ new PagedStorage<>(0, createPage("a", "b", "c"), 10);
+
+ PagedStorage<Integer, String> storageWithPlaceholder =
+ new PagedStorage<>(0, createPage("a", "b", "c"), 10);
+ storageWithPlaceholder.allocatePlaceholders(3, 0, 3,
+ /* ignored */ mock(PagedStorage.Callback.class));
+
+ // even though one has placeholders, and null counts are different...
+ assertEquals(10, storageNoPlaceholder.getTrailingNullCount());
+ assertEquals(7, storageWithPlaceholder.getTrailingNullCount());
+
+ // ... should be no interactions, since content still same
+ validateTwoListDiff(
+ storageNoPlaceholder,
+ storageWithPlaceholder,
new CallbackValidator() {
@Override
public void validate(ListUpdateCallback callback) {
@@ -79,8 +115,8 @@ public class ContiguousDiffHelperTest {
@Test
public void appendFill() {
validateTwoListDiff(
- new StringPagedList(5, 5, "a", "b"),
- new StringPagedList(5, 4, "a", "b", "c"),
+ new PagedStorage<>(5, createPage("a", "b"), 5),
+ new PagedStorage<>(5, createPage("a", "b", "c"), 4),
new CallbackValidator() {
@Override
public void validate(ListUpdateCallback callback) {
@@ -96,8 +132,8 @@ public class ContiguousDiffHelperTest {
@Test
public void prependFill() {
validateTwoListDiff(
- new StringPagedList(5, 5, "b", "c"),
- new StringPagedList(4, 5, "a", "b", "c"),
+ new PagedStorage<>(5, createPage("b", "c"), 5),
+ new PagedStorage<>(4, createPage("a", "b", "c"), 5),
new CallbackValidator() {
@Override
public void validate(ListUpdateCallback callback) {
@@ -113,8 +149,8 @@ public class ContiguousDiffHelperTest {
@Test
public void change() {
validateTwoListDiff(
- new StringPagedList(5, 5, "a1", "b1", "c1"),
- new StringPagedList(5, 5, "a2", "b1", "c2"),
+ new PagedStorage<>(5, createPage("a1", "b1", "c1"), 5),
+ new PagedStorage<>(5, createPage("a2", "b1", "c2"), 5),
new CallbackValidator() {
@Override
public void validate(ListUpdateCallback callback) {
@@ -125,4 +161,5 @@ public class ContiguousDiffHelperTest {
}
);
}
+
}
diff --git a/android/arch/paging/PositionalDataSource.java b/android/arch/paging/PositionalDataSource.java
index deb51e94..c538cb60 100644
--- a/android/arch/paging/PositionalDataSource.java
+++ b/android/arch/paging/PositionalDataSource.java
@@ -42,6 +42,17 @@ import java.util.List;
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> {
+
+ /**
+ * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED.
+ *
+ * @return number of items that this DataSource can provide in total, or COUNT_UNDEFINED
+ * if difficult or undesired to compute.
+ */
+ public int countItems() {
+ return COUNT_UNDEFINED;
+ }
+
@Nullable
@Override
List<Value> loadAfterImpl(int currentEndIndex, @NonNull Value currentEndItem, int pageSize) {
@@ -55,16 +66,7 @@ public abstract class PositionalDataSource<Value> extends ContiguousDataSource<I
return loadBefore(currentBeginIndex - 1, pageSize);
}
-
- /**
- * Load initial data, starting after the passed position.
- *
- * @param position Index just before the data to be loaded.
- * @param initialLoadSize Suggested number of items to load.
- * @return List of initial items, representing data starting at position. Null if the
- * DataSource is no longer valid, and should not be queried again.
- * @hide
- */
+ /** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
@@ -118,6 +120,9 @@ public abstract class PositionalDataSource<Value> extends ContiguousDataSource<I
@Override
Integer getKey(int position, Value item) {
+ if (position < 0) {
+ return null;
+ }
return position;
}
}
diff --git a/android/arch/paging/SnapshotPagedList.java b/android/arch/paging/SnapshotPagedList.java
new file mode 100644
index 00000000..7e965a0f
--- /dev/null
+++ b/android/arch/paging/SnapshotPagedList.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.arch.paging;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+class SnapshotPagedList<T> extends PagedList<T> {
+ private final boolean mContiguous;
+ private final Object mLastKey;
+
+ SnapshotPagedList(@NonNull PagedList<T> pagedList) {
+ super(pagedList.mStorage.snapshot(),
+ pagedList.mMainThreadExecutor,
+ pagedList.mBackgroundThreadExecutor,
+ pagedList.mConfig);
+ mContiguous = pagedList.isContiguous();
+ mLastKey = pagedList.getLastKey();
+ }
+
+ @Override
+ public boolean isImmutable() {
+ return true;
+ }
+
+ @Override
+ public boolean isDetached() {
+ return true;
+ }
+
+ @Override
+ boolean isContiguous() {
+ return mContiguous;
+ }
+
+ @Nullable
+ @Override
+ public Object getLastKey() {
+ return mLastKey;
+ }
+
+ @Override
+ void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> storageSnapshot,
+ @NonNull Callback callback) {
+ }
+
+ @Override
+ void loadAroundInternal(int index) {
+ }
+}
diff --git a/android/arch/paging/SparseDiffHelper.java b/android/arch/paging/SparseDiffHelper.java
deleted file mode 100644
index fe478973..00000000
--- a/android/arch/paging/SparseDiffHelper.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.v7.recyclerview.extensions.DiffCallback;
-import android.support.v7.util.DiffUtil;
-import android.support.v7.util.ListUpdateCallback;
-
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class SparseDiffHelper {
- private SparseDiffHelper() {
- }
-
- @NonNull
- static <T> DiffUtil.DiffResult computeDiff(
- final PageArrayList<T> oldList, final PageArrayList<T> newList,
- final DiffCallback<T> diffCallback, boolean detectMoves) {
-
- if (!oldList.isImmutable()) {
- throw new IllegalArgumentException("list must be immutable to safely perform diff");
- }
- if (!newList.isImmutable()) {
- throw new IllegalArgumentException("list must be immutable to safely perform diff");
- }
- return DiffUtil.calculateDiff(new DiffUtil.Callback() {
- @Nullable
- @Override
- public Object getChangePayload(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.get(oldItemPosition);
- T newItem = newList.get(newItemPosition);
- if (oldItem == null || newItem == null) {
- return null;
- }
- return diffCallback.getChangePayload(oldItem, newItem);
- }
-
- @Override
- public int getOldListSize() {
- return oldList.size();
- }
-
- @Override
- public int getNewListSize() {
- return newList.size();
- }
-
- @Override
- public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.get(oldItemPosition);
- T newItem = newList.get(newItemPosition);
- if (oldItem == newItem) {
- return true;
- }
- if (oldItem == null || newItem == null) {
- return false;
- }
- return diffCallback.areItemsTheSame(oldItem, newItem);
- }
-
- @Override
- public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
- T oldItem = oldList.get(oldItemPosition);
- T newItem = newList.get(newItemPosition);
- if (oldItem == newItem) {
- return true;
- }
- if (oldItem == null || newItem == null) {
- return false;
- }
-
- return diffCallback.areContentsTheSame(oldItem, newItem);
- }
- }, detectMoves);
- }
-
- static <T> void dispatchDiff(ListUpdateCallback callback,
- final DiffUtil.DiffResult diffResult) {
- // Simple case, dispatch & return
- diffResult.dispatchUpdatesTo(callback);
- }
-}
diff --git a/android/arch/paging/StringPagedList.java b/android/arch/paging/StringPagedList.java
index 5318d38c..880d5e9b 100644
--- a/android/arch/paging/StringPagedList.java
+++ b/android/arch/paging/StringPagedList.java
@@ -16,10 +16,60 @@
package android.arch.paging;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
import java.util.Arrays;
-public class StringPagedList extends NullPaddedList<String> {
- public StringPagedList(int leadingNulls, int trailingNulls, String... items) {
- super(leadingNulls, Arrays.asList(items), trailingNulls);
+public class StringPagedList extends PagedList<String> implements PagedStorage.Callback {
+ StringPagedList(int leadingNulls, int trailingNulls, String... items) {
+ super(new PagedStorage<Integer, String>(),
+ null, null, null);
+ PagedStorage<Integer, String> keyedStorage = (PagedStorage<Integer, String>) mStorage;
+ keyedStorage.init(leadingNulls,
+ new Page<Integer, String>(null, Arrays.asList(items), null),
+ trailingNulls,
+ 0,
+ this);
+ }
+
+ @Override
+ boolean isContiguous() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public Object getLastKey() {
+ return null;
+ }
+
+ @Override
+ protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<String> storageSnapshot,
+ @NonNull Callback callback) {
+ }
+
+ @Override
+ protected void loadAroundInternal(int index) {
+ }
+
+ @Override
+ public void onInitialized(int count) {
+ }
+
+ @Override
+ public void onPagePrepended(int leadingNulls, int changed, int added) {
+ }
+
+ @Override
+ public void onPageAppended(int endPosition, int changed, int added) {
+ }
+
+ @Override
+ public void onPagePlaceholderInserted(int pageIndex) {
+ }
+
+ @Override
+ public void onPageInserted(int start, int count) {
}
}
diff --git a/android/arch/paging/TestExecutor.java b/android/arch/paging/TestExecutor.java
index 30809c3e..976f7df5 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);
}
- boolean executeAll() {
+ public 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 36be423d..61dead3a 100644
--- a/android/arch/paging/TiledDataSource.java
+++ b/android/arch/paging/TiledDataSource.java
@@ -19,6 +19,7 @@ package android.arch.paging;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
+import java.util.Collections;
import java.util.List;
/**
@@ -92,7 +93,6 @@ 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();
@Override
@@ -118,7 +118,61 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
@WorkerThread
public abstract List<Type> loadRange(int startPosition, int count);
- final List<Type> loadRangeWrapper(int startPosition, int count) {
+ /**
+ * blocking, and splits pages
+ */
+ void loadRangeInitial(int startPosition, int count, int pageSize, int itemCount,
+ PageResult.Receiver<Integer, Type> receiver) {
+
+ if (itemCount == 0) {
+ // no data to load, just immediately return empty
+ receiver.onPageResult(new PageResult<>(
+ PageResult.INIT, new Page<Integer, Type>(Collections.<Type>emptyList()),
+ 0, 0, startPosition));
+ return;
+ }
+
+
+ List<Type> list = loadRangeWrapper(startPosition, count);
+
+ count = Math.min(count, itemCount - startPosition);
+
+ if (list == null) {
+ // invalid data, pass to receiver
+ receiver.onPageResult(new PageResult<Integer, Type>(
+ PageResult.INIT, null, 0, 0, startPosition));
+ return;
+ }
+
+ if (list.size() != count) {
+ throw new IllegalStateException("Invalid list, requested size: " + count
+ + ", returned size: " + list.size());
+ }
+
+ // emit the results as multiple pages
+ int pageCount = (count + (pageSize - 1)) / pageSize;
+ for (int i = 0; i < pageCount; i++) {
+ int beginInclusive = i * pageSize;
+ int endExclusive = Math.min(count, (i + 1) * pageSize);
+
+ Page<Integer, Type> page = new Page<>(list.subList(beginInclusive, endExclusive));
+
+ int leadingNulls = startPosition + beginInclusive;
+ int trailingNulls = itemCount - leadingNulls - page.items.size();
+ receiver.onPageResult(new PageResult<>(
+ PageResult.INIT, page, leadingNulls, trailingNulls, 0));
+ }
+ }
+
+ void loadRange(int startPosition, int count, PageResult.Receiver<Integer, Type> receiver) {
+ List<Type> list = loadRangeWrapper(startPosition, count);
+
+ Page<Integer, Type> page = list != null ? new Page<Integer, Type>(list) : null;
+ receiver.postOnPageResult(new PageResult<>(
+ PageResult.TILE, page, startPosition, 0, 0));
+ }
+
+ private List<Type> loadRangeWrapper(int startPosition, int count) {
if (isInvalid()) {
return null;
}
diff --git a/android/arch/paging/TiledPagedList.java b/android/arch/paging/TiledPagedList.java
index a2fc064b..c45d029d 100644
--- a/android/arch/paging/TiledPagedList.java
+++ b/android/arch/paging/TiledPagedList.java
@@ -16,219 +16,173 @@
package android.arch.paging;
+import android.support.annotation.AnyThread;
+import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
-import java.lang.ref.WeakReference;
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.List;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class TiledPagedList<T> extends PageArrayList<T> {
+class TiledPagedList<T> extends PagedList<T>
+ implements PagedStorage.Callback {
private final TiledDataSource<T> mDataSource;
- private final Executor mMainThreadExecutor;
- private final Executor mBackgroundThreadExecutor;
- private final Config mConfig;
- @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
- private final List<T> mLoadingPlaceholder = new AbstractList<T>() {
- @Override
- public T get(int i) {
- return null;
- }
+ @SuppressWarnings("unchecked")
+ private final PagedStorage<Integer, T> mKeyedStorage = (PagedStorage<Integer, T>) mStorage;
+ private final PageResult.Receiver<Integer, T> mReceiver =
+ new PageResult.Receiver<Integer, T>() {
+ @AnyThread
@Override
- public int size() {
- return 0;
+ public void postOnPageResult(@NonNull final PageResult<Integer, T> pageResult) {
+ // NOTE: if we're already on main thread, this can delay page receive by a frame
+ mMainThreadExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ onPageResult(pageResult);
+ }
+ });
}
- };
- private int mLastLoad = -1;
+ @MainThread
+ @Override
+ public void onPageResult(@NonNull PageResult<Integer, T> pageResult) {
+ if (pageResult.page == null) {
+ detach();
+ return;
+ }
- private AtomicBoolean mDetached = new AtomicBoolean(false);
+ if (isDetached()) {
+ // No op, have detached
+ return;
+ }
- private ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+ if (mStorage.getPageCount() == 0) {
+ mKeyedStorage.init(
+ pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls,
+ pageResult.positionOffset, TiledPagedList.this);
+ } else {
+ mKeyedStorage.insertPage(pageResult.leadingNulls, pageResult.page,
+ TiledPagedList.this);
+ }
+ }
+ };
@WorkerThread
TiledPagedList(@NonNull TiledDataSource<T> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
- Config config,
+ @NonNull Config config,
int position) {
- super(config.mPageSize, dataSource.countItems());
-
+ super(new PagedStorage<Integer, T>(),
+ mainThreadExecutor, backgroundThreadExecutor, config);
mDataSource = dataSource;
- mMainThreadExecutor = mainThreadExecutor;
- mBackgroundThreadExecutor = backgroundThreadExecutor;
- mConfig = config;
-
- position = Math.min(Math.max(0, position), mCount);
-
- int firstPage = position / mPageSize;
- List<T> firstPageData = dataSource.loadRangeWrapper(firstPage * mPageSize, mPageSize);
- if (firstPageData != null) {
- mPageIndexOffset = firstPage;
- mPages.add(firstPageData);
- mLastLoad = position;
- } else {
- detach();
- return;
- }
- int secondPage = (position % mPageSize < mPageSize / 2) ? firstPage - 1 : firstPage + 1;
- if (secondPage < 0 || secondPage > mMaxPageCount) {
- // no second page to load
- return;
- }
- List<T> secondPageData = dataSource.loadRangeWrapper(secondPage * mPageSize, mPageSize);
- if (secondPageData != null) {
- boolean before = secondPage < firstPage;
- mPages.add(before ? 0 : 1, secondPageData);
- if (before) {
- mPageIndexOffset--;
- }
- return;
- }
- detach();
- }
+ final int pageSize = mConfig.mPageSize;
- @Override
- public void loadAround(int index) {
- mLastLoad = index;
-
- int minimumPage = Math.max((index - mConfig.mPrefetchDistance) / mPageSize, 0);
- int maximumPage = Math.min((index + mConfig.mPrefetchDistance) / mPageSize,
- mMaxPageCount - 1);
+ final int itemCount = mDataSource.countItems();
+ final int firstLoadSize = Math.min(itemCount,
+ (Math.max(mConfig.mInitialLoadSizeHint / pageSize, 2)) * pageSize);
+ final int firstLoadPosition = computeFirstLoadPosition(
+ position, firstLoadSize, pageSize, itemCount);
- if (minimumPage < mPageIndexOffset) {
- for (int i = 0; i < mPageIndexOffset - minimumPage; i++) {
- mPages.add(0, null);
- }
- mPageIndexOffset = minimumPage;
- }
- if (maximumPage >= mPageIndexOffset + mPages.size()) {
- for (int i = mPages.size(); i <= maximumPage - mPageIndexOffset; i++) {
- mPages.add(mPages.size(), null);
- }
- }
- for (int i = minimumPage; i <= maximumPage; i++) {
- scheduleLoadPage(i);
- }
+ mDataSource.loadRangeInitial(firstLoadPosition, firstLoadSize, pageSize,
+ itemCount, mReceiver);
}
- private void scheduleLoadPage(final int pageIndex) {
- final int localPageIndex = pageIndex - mPageIndexOffset;
+ static int computeFirstLoadPosition(int position, int firstLoadSize, int pageSize, int size) {
+ int idealStart = position - firstLoadSize / 2;
- if (mPages.get(localPageIndex) != null) {
- // page is present in list, and non-null - don't need to load
- return;
- }
- mPages.set(localPageIndex, mLoadingPlaceholder);
+ int roundedPageStart = Math.round(idealStart / pageSize) * pageSize;
- mBackgroundThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (mDetached.get()) {
- return;
- }
- final List<T> data = mDataSource.loadRangeWrapper(
- pageIndex * mPageSize, mPageSize);
- if (data != null) {
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (mDetached.get()) {
- return;
- }
- loadPageImpl(pageIndex, data);
- }
- });
- } else {
- detach();
- }
- }
- });
+ // minimum start position is 0
+ roundedPageStart = Math.max(0, roundedPageStart);
- }
+ // maximum start pos is that which will encompass end of list
+ int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize;
+ roundedPageStart = Math.min(maximumLoadPage, roundedPageStart);
- private void loadPageImpl(int pageIndex, List<T> data) {
- int localPageIndex = pageIndex - mPageIndexOffset;
+ return roundedPageStart;
+ }
- if (mPages.get(localPageIndex) != mLoadingPlaceholder) {
- throw new IllegalStateException("Data inserted before requested.");
- }
- mPages.set(localPageIndex, data);
- for (WeakReference<Callback> weakRef : mCallbacks) {
- Callback callback = weakRef.get();
- if (callback != null) {
- callback.onChanged(pageIndex * mPageSize, data.size());
- }
- }
+ @Override
+ boolean isContiguous() {
+ return false;
}
+ @Nullable
@Override
- public boolean isImmutable() {
- // TODO: consider counting loaded pages, return true if mLoadedPages == mMaxPageCount
- // Note: could at some point want to support growing past max count, or grow dynamically
- return isDetached();
+ public Object getLastKey() {
+ return mLastLoad;
}
@Override
- public void addWeakCallback(@Nullable PagedList<T> previousSnapshot,
+ protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot,
@NonNull Callback callback) {
- PageArrayList<T> snapshot = (PageArrayList<T>) previousSnapshot;
- if (snapshot != this && snapshot != null) {
- // loop through each page and signal the callback for any pages that are present now,
- // but not in the snapshot.
- for (int i = 0; i < mPages.size(); i++) {
- int pageIndex = i + mPageIndexOffset;
- int pageCount = 0;
- // count number of consecutive pages that were added since the snapshot...
- while (pageCount < mPages.size()
- && hasPage(pageIndex + pageCount)
- && !snapshot.hasPage(pageIndex + pageCount)) {
- pageCount++;
- }
- // and signal them all at once to the callback
- if (pageCount > 0) {
- callback.onChanged(pageIndex * mPageSize, mPageSize * pageCount);
- i += pageCount - 1;
- }
+ //noinspection UnnecessaryLocalVariable
+ final PagedStorage<?, T> snapshot = pagedListSnapshot.mStorage;
+
+ // loop through each page and signal the callback for any pages that are present now,
+ // but not in the snapshot.
+ final int pageSize = mConfig.mPageSize;
+ final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize;
+ final int pageCount = mStorage.getPageCount();
+ for (int i = 0; i < pageCount; i++) {
+ int pageIndex = i + leadingNullPages;
+ int updatedPages = 0;
+ // count number of consecutive pages that were added since the snapshot...
+ while (updatedPages < mStorage.getPageCount()
+ && mStorage.hasPage(pageSize, pageIndex + updatedPages)
+ && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) {
+ updatedPages++;
+ }
+ // and signal them all at once to the callback
+ if (updatedPages > 0) {
+ callback.onChanged(pageIndex * pageSize, pageSize * updatedPages);
+ i += updatedPages - 1;
}
}
- mCallbacks.add(new WeakReference<>(callback));
}
@Override
- public void removeWeakCallback(@NonNull Callback callback) {
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- Callback currentCallback = mCallbacks.get(i).get();
- if (currentCallback == null || currentCallback == callback) {
- mCallbacks.remove(i);
- }
- }
+ protected void loadAroundInternal(int index) {
+ mStorage.allocatePlaceholders(index, mConfig.mPrefetchDistance, mConfig.mPageSize, this);
}
@Override
- public boolean isDetached() {
- return mDetached.get();
+ public void onInitialized(int count) {
+ notifyInserted(0, count);
}
@Override
- public void detach() {
- mDetached.set(true);
+ public void onPagePrepended(int leadingNulls, int changed, int added) {
+ throw new IllegalStateException("Contiguous callback on TiledPagedList");
}
- @Nullable
@Override
- public Object getLastKey() {
- return mLastLoad;
+ public void onPageAppended(int endPosition, int changed, int added) {
+ throw new IllegalStateException("Contiguous callback on TiledPagedList");
+ }
+
+ @Override
+ public void onPagePlaceholderInserted(final int pageIndex) {
+ // placeholder means initialize a load
+ mBackgroundThreadExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ if (isDetached()) {
+ return;
+ }
+ final int pageSize = mConfig.mPageSize;
+ mDataSource.loadRange(pageIndex * pageSize, pageSize, mReceiver);
+ }
+ });
+ }
+
+ @Override
+ public void onPageInserted(int start, int count) {
+ notifyChanged(start, count);
}
}
diff --git a/android/arch/paging/TiledPagedListTest.java b/android/arch/paging/TiledPagedListTest.java
index 4ad02e12..22bfd1ff 100644
--- a/android/arch/paging/TiledPagedListTest.java
+++ b/android/arch/paging/TiledPagedListTest.java
@@ -78,75 +78,107 @@ public class TiledPagedListTest {
}
}
- private void verifyRange(PageArrayList<Item> list, Integer... loadedPages) {
+ private void verifyRange(List<Item> list, Integer... loadedPages) {
List<Integer> loadedPageList = Arrays.asList(loadedPages);
assertEquals(ITEMS.size(), list.size());
for (int i = 0; i < list.size(); i++) {
if (loadedPageList.contains(i / PAGE_SIZE)) {
- assertSame(ITEMS.get(i), list.get(i));
+ assertSame("Index " + i, ITEMS.get(i), list.get(i));
} else {
- assertEquals(null, list.get(i));
+ assertEquals("Index " + i, null, list.get(i));
}
}
}
- private TiledPagedList<Item> createTiledPagedList(int loadPosition) {
- return createTiledPagedList(loadPosition, PAGE_SIZE);
+ private TiledPagedList<Item> createTiledPagedList(int loadPosition, int initPages) {
+ return createTiledPagedList(loadPosition, initPages, PAGE_SIZE);
}
- private TiledPagedList<Item> createTiledPagedList(int loadPosition, int prefetchDistance) {
+ private TiledPagedList<Item> createTiledPagedList(int loadPosition, int initPages,
+ int prefetchDistance) {
TestTiledSource source = new TestTiledSource();
return new TiledPagedList<>(
source, mMainThread, mBackgroundThread,
new PagedList.Config.Builder()
.setPageSize(PAGE_SIZE)
+ .setInitialLoadSizeHint(PAGE_SIZE * initPages)
.setPrefetchDistance(prefetchDistance)
.build(),
loadPosition);
}
@Test
- public void initialLoad() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0);
- verifyRange(pagedList, 0);
+ public void computeFirstLoadPosition_zero() {
+ assertEquals(0, TiledPagedList.computeFirstLoadPosition(0, 30, 10, 100));
+ }
+
+ @Test
+ public void computeFirstLoadPosition_requestedPositionIncluded() {
+ assertEquals(0, TiledPagedList.computeFirstLoadPosition(10, 10, 10, 100));
+ }
+
+ @Test
+ public void computeFirstLoadPosition_endAdjusted() {
+ assertEquals(70, TiledPagedList.computeFirstLoadPosition(99, 30, 10, 100));
+ }
+
+ @Test
+ public void initialLoad_onePage() {
+ TiledPagedList<Item> pagedList = createTiledPagedList(0, 1);
+ verifyRange(pagedList, 0, 1);
+ }
+
+ @Test
+ public void initialLoad_onePageOffset() {
+ TiledPagedList<Item> pagedList = createTiledPagedList(10, 1);
+ verifyRange(pagedList, 0, 1);
+ }
+
+ @Test
+ public void initialLoad_full() {
+ TiledPagedList<Item> pagedList = createTiledPagedList(0, 100);
+ verifyRange(pagedList, 0, 1, 2, 3, 4);
}
@Test
public void initialLoad_end() {
- TiledPagedList<Item> pagedList = createTiledPagedList(44);
+ TiledPagedList<Item> pagedList = createTiledPagedList(44, 2);
verifyRange(pagedList, 3, 4);
}
@Test
public void initialLoad_multiple() {
- TiledPagedList<Item> pagedList = createTiledPagedList(9);
+ TiledPagedList<Item> pagedList = createTiledPagedList(9, 2);
verifyRange(pagedList, 0, 1);
}
@Test
public void initialLoad_offset() {
- TiledPagedList<Item> pagedList = createTiledPagedList(41);
+ TiledPagedList<Item> pagedList = createTiledPagedList(41, 2);
verifyRange(pagedList, 3, 4);
}
@Test
public void append() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0);
+ TiledPagedList<Item> pagedList = createTiledPagedList(0, 1);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
- verifyRange(pagedList, 0);
+ verifyRange(pagedList, 0, 1);
verifyZeroInteractions(callback);
- pagedList.loadAround(5);
- drain();
+ pagedList.loadAround(15);
verifyRange(pagedList, 0, 1);
- verify(callback).onChanged(10, 10);
+
+ drain();
+
+ verifyRange(pagedList, 0, 1, 2);
+ verify(callback).onChanged(20, 10);
verifyNoMoreInteractions(callback);
}
@Test
public void prepend() {
- TiledPagedList<Item> pagedList = createTiledPagedList(44);
+ TiledPagedList<Item> pagedList = createTiledPagedList(44, 2);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
verifyRange(pagedList, 3, 4);
@@ -162,16 +194,16 @@ public class TiledPagedListTest {
@Test
public void loadWithGap() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0);
+ TiledPagedList<Item> pagedList = createTiledPagedList(0, 1);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
- verifyRange(pagedList, 0);
+ verifyRange(pagedList, 0, 1);
verifyZeroInteractions(callback);
pagedList.loadAround(44);
drain();
- verifyRange(pagedList, 0, 3, 4);
+ verifyRange(pagedList, 0, 1, 3, 4);
verify(callback).onChanged(30, 10);
verify(callback).onChanged(40, 5);
verifyNoMoreInteractions(callback);
@@ -179,58 +211,56 @@ public class TiledPagedListTest {
@Test
public void tinyPrefetchTest() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0, 1);
+ TiledPagedList<Item> pagedList = createTiledPagedList(0, 1, 1);
PagedList.Callback callback = mock(PagedList.Callback.class);
pagedList.addWeakCallback(null, callback);
- verifyRange(pagedList, 0); // just 4 loaded
+ verifyRange(pagedList, 0, 1);
verifyZeroInteractions(callback);
- pagedList.loadAround(23);
+ pagedList.loadAround(33);
drain();
- verifyRange(pagedList, 0, 2);
- verify(callback).onChanged(20, 10);
+ verifyRange(pagedList, 0, 1, 3);
+ verify(callback).onChanged(30, 10);
verifyNoMoreInteractions(callback);
pagedList.loadAround(44);
drain();
- verifyRange(pagedList, 0, 2, 4);
+ verifyRange(pagedList, 0, 1, 3, 4);
verify(callback).onChanged(40, 5);
verifyNoMoreInteractions(callback);
}
@Test
public void appendCallbackAddedLate() {
- TiledPagedList<Item> pagedList = createTiledPagedList(0, 0);
- verifyRange(pagedList, 0);
-
- pagedList.loadAround(15);
- drain();
+ TiledPagedList<Item> pagedList = createTiledPagedList(0, 1, 0);
verifyRange(pagedList, 0, 1);
- // snapshot at 20 items
- PageArrayList<Item> snapshot = (PageArrayList<Item>) pagedList.snapshot();
- verifyRange(snapshot, 0, 1);
+ pagedList.loadAround(25);
+ drain();
+ verifyRange(pagedList, 0, 1, 2);
+ // snapshot at 30 items
+ List<Item> snapshot = pagedList.snapshot();
+ verifyRange(snapshot, 0, 1, 2);
- pagedList.loadAround(25);
pagedList.loadAround(35);
+ pagedList.loadAround(44);
drain();
- verifyRange(pagedList, 0, 1, 2, 3);
- verifyRange(snapshot, 0, 1);
+ verifyRange(pagedList, 0, 1, 2, 3, 4);
+ verifyRange(snapshot, 0, 1, 2);
PagedList.Callback callback = mock(
PagedList.Callback.class);
pagedList.addWeakCallback(snapshot, callback);
- verify(callback).onChanged(20, 20);
+ verify(callback).onChanged(30, 20);
verifyNoMoreInteractions(callback);
}
-
@Test
public void prependCallbackAddedLate() {
- TiledPagedList<Item> pagedList = createTiledPagedList(44, 0);
+ TiledPagedList<Item> pagedList = createTiledPagedList(44, 2, 0);
verifyRange(pagedList, 3, 4);
pagedList.loadAround(25);
@@ -238,10 +268,9 @@ public class TiledPagedListTest {
verifyRange(pagedList, 2, 3, 4);
// snapshot at 30 items
- PageArrayList<Item> snapshot = (PageArrayList<Item>) pagedList.snapshot();
+ List<Item> snapshot = pagedList.snapshot();
verifyRange(snapshot, 2, 3, 4);
-
pagedList.loadAround(15);
pagedList.loadAround(5);
drain();
@@ -272,10 +301,11 @@ public class TiledPagedListTest {
assertTrue(pagedList.isContiguous());
- ContiguousPagedList<Item> contiguousPagedList = (ContiguousPagedList<Item>) pagedList;
- assertEquals(0, contiguousPagedList.getLeadingNullCount());
- assertEquals(PAGE_SIZE, contiguousPagedList.mList.size());
- assertEquals(0, contiguousPagedList.getTrailingNullCount());
+ ContiguousPagedList<Integer, Item> contiguousPagedList =
+ (ContiguousPagedList<Integer, Item>) pagedList;
+ assertEquals(0, contiguousPagedList.mStorage.getLeadingNullCount());
+ assertEquals(PAGE_SIZE, contiguousPagedList.mStorage.getStorageCount());
+ assertEquals(0, contiguousPagedList.mStorage.getTrailingNullCount());
}
private void drain() {
diff --git a/android/arch/persistence/room/InvalidationTracker.java b/android/arch/persistence/room/InvalidationTracker.java
index 45ec0289..b31dc13a 100644
--- a/android/arch/persistence/room/InvalidationTracker.java
+++ b/android/arch/persistence/room/InvalidationTracker.java
@@ -219,7 +219,7 @@ public class InvalidationTracker {
*
* @param observer The observer which listens the database for changes.
*/
- public void addObserver(Observer observer) {
+ public void addObserver(@NonNull Observer observer) {
final String[] tableNames = observer.mTables;
int[] tableIds = new int[tableNames.length];
final int size = tableNames.length;
@@ -265,7 +265,7 @@ public class InvalidationTracker {
* @param observer The observer to remove.
*/
@SuppressWarnings("WeakerAccess")
- public void removeObserver(final Observer observer) {
+ public void removeObserver(@NonNull final Observer observer) {
ObserverWrapper wrapper;
synchronized (mObserverMap) {
wrapper = mObserverMap.remove(observer);
diff --git a/android/arch/persistence/room/Relation.java b/android/arch/persistence/room/Relation.java
index 72066992..d55bbfe8 100644
--- a/android/arch/persistence/room/Relation.java
+++ b/android/arch/persistence/room/Relation.java
@@ -28,6 +28,8 @@ import java.lang.annotation.Target;
* <pre>
* {@literal @}Entity
* public class Pet {
+ * {@literal @} PrimaryKey
+ * int id;
* int userId;
* String name;
* // other fields
@@ -41,8 +43,8 @@ import java.lang.annotation.Target;
*
* {@literal @}Dao
* public interface UserPetDao {
- * {@literal @}Query("SELECT id, name from User WHERE age &gt; :minAge")
- * public List&lt;UserNameAndAllPets&gt; loadUserAndPets(int minAge);
+ * {@literal @}Query("SELECT id, name from User")
+ * public List&lt;UserNameAndAllPets&gt; loadUserAndPets();
* }
* </pre>
* <p>
@@ -63,16 +65,16 @@ import java.lang.annotation.Target;
* {@literal @}Embedded
* public User user;
* {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
- * public List<PetNameAndId> pets;
+ * public List&lt;PetNameAndId&gt; pets;
* }
* {@literal @}Dao
* public interface UserPetDao {
- * {@literal @}Query("SELECT * from User WHERE age &gt; :minAge")
- * public List&lt;UserAllPets&gt; loadUserAndPets(int minAge);
+ * {@literal @}Query("SELECT * from User")
+ * public List&lt;UserAllPets&gt; loadUserAndPets();
* }
* </pre>
* <p>
- * In the example above, {@code PetNameAndId} is a regular but all of fields are fetched
+ * In the example above, {@code PetNameAndId} is a regular Pojo but all of fields are fetched
* from the {@code entity} defined in the {@code @Relation} annotation (<i>Pet</i>).
* {@code PetNameAndId} could also define its own relations all of which would also be fetched
* automatically.
@@ -85,7 +87,7 @@ import java.lang.annotation.Target;
* public User user;
* {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class,
* projection = {"name"})
- * public List<String> petNames;
+ * public List&lt;String&gt; petNames;
* }
* </pre>
* <p>
@@ -93,7 +95,7 @@ import java.lang.annotation.Target;
* cannot have relations. This is a design decision to avoid common pitfalls in {@link Entity}
* setups. You can read more about it in the main Room documentation. When loading data, you can
* simply work around this limitation by creating Pojo classes that extend the {@link Entity}.
- *
+ * <p>
* Note that the {@code @Relation} annotated field cannot be a constructor parameter, it must be
* public or have a public setter.
*/
diff --git a/android/arch/persistence/room/Room.java b/android/arch/persistence/room/Room.java
index 8ce4be0c..2850b55e 100644
--- a/android/arch/persistence/room/Room.java
+++ b/android/arch/persistence/room/Room.java
@@ -43,6 +43,7 @@ public class Room {
* @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
*/
@SuppressWarnings("WeakerAccess")
+ @NonNull
public static <T extends RoomDatabase> RoomDatabase.Builder<T> databaseBuilder(
@NonNull Context context, @NonNull Class<T> klass, @NonNull String name) {
//noinspection ConstantConditions
@@ -65,6 +66,7 @@ public class Room {
* @param <T> The type of the database class.
* @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
*/
+ @NonNull
public static <T extends RoomDatabase> RoomDatabase.Builder<T> inMemoryDatabaseBuilder(
@NonNull Context context, @NonNull Class<T> klass) {
return new RoomDatabase.Builder<>(context, klass, null);
diff --git a/android/arch/persistence/room/RoomDatabase.java b/android/arch/persistence/room/RoomDatabase.java
index cdad868d..8c940246 100644
--- a/android/arch/persistence/room/RoomDatabase.java
+++ b/android/arch/persistence/room/RoomDatabase.java
@@ -49,7 +49,7 @@ import java.util.concurrent.locks.ReentrantLock;
*
* @see Database
*/
-@SuppressWarnings({"unused", "WeakerAccess"})
+//@SuppressWarnings({"unused", "WeakerAccess"})
public abstract class RoomDatabase {
private static final String DB_IMPL_SUFFIX = "_Impl";
// set by the generated open helper.
@@ -153,7 +153,9 @@ public abstract class RoomDatabase {
*
* @hide
*/
+ @SuppressWarnings("WeakerAccess")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ // used in generated code
public void assertNotMainThread() {
if (mAllowMainThreadQueries) {
return;
@@ -298,6 +300,7 @@ public abstract class RoomDatabase {
* @return True if there is an active transaction in current thread, false otherwise.
* @see SupportSQLiteDatabase#inTransaction()
*/
+ @SuppressWarnings("WeakerAccess")
public boolean inTransaction() {
return mOpenHelper.getWritableDatabase().inTransaction();
}
@@ -307,7 +310,6 @@ public abstract class RoomDatabase {
*
* @param <T> The type of the abstract database class.
*/
- @SuppressWarnings("unused")
public static class Builder<T extends RoomDatabase> {
private final Class<T> mDatabaseClass;
private final String mName;
@@ -337,7 +339,8 @@ public abstract class RoomDatabase {
* @param factory The factory to use to access the database.
* @return this
*/
- public Builder<T> openHelperFactory(SupportSQLiteOpenHelper.Factory factory) {
+ @NonNull
+ public Builder<T> openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) {
mFactory = factory;
return this;
}
@@ -361,6 +364,7 @@ public abstract class RoomDatabase {
* changes.
* @return this
*/
+ @NonNull
public Builder<T> addMigrations(Migration... migrations) {
mMigrationContainer.addMigrations(migrations);
return this;
@@ -378,6 +382,7 @@ public abstract class RoomDatabase {
*
* @return this
*/
+ @NonNull
public Builder<T> allowMainThreadQueries() {
mAllowMainThreadQueries = true;
return this;
@@ -400,6 +405,7 @@ public abstract class RoomDatabase {
*
* @return this
*/
+ @NonNull
public Builder<T> fallbackToDestructiveMigration() {
mRequireMigration = false;
return this;
@@ -411,6 +417,7 @@ public abstract class RoomDatabase {
* @param callback The callback.
* @return this
*/
+ @NonNull
public Builder<T> addCallback(@NonNull Callback callback) {
if (mCallbacks == null) {
mCallbacks = new ArrayList<>();
@@ -427,6 +434,7 @@ public abstract class RoomDatabase {
*
* @return A new database instance.
*/
+ @NonNull
public T build() {
//noinspection ConstantConditions
if (mContext == null) {
@@ -493,6 +501,7 @@ public abstract class RoomDatabase {
* @return An ordered list of {@link Migration} objects that should be run to migrate
* between the given versions. If a migration path cannot be found, returns {@code null}.
*/
+ @SuppressWarnings("WeakerAccess")
@Nullable
public List<Migration> findMigrationPath(int start, int end) {
if (start == end) {
diff --git a/android/arch/persistence/room/RoomWarnings.java b/android/arch/persistence/room/RoomWarnings.java
index c64be967..f05e6be2 100644
--- a/android/arch/persistence/room/RoomWarnings.java
+++ b/android/arch/persistence/room/RoomWarnings.java
@@ -125,4 +125,12 @@ public class RoomWarnings {
* annotation.
*/
public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+
+ /**
+ * Reported when a @Query method returns a Pojo that has relations but the method is not
+ * annotated with @Transaction. Relations are run as separate queries and if the query is not
+ * run inside a transaction, it might return inconsistent results from the database.
+ */
+ public static final String RELATION_QUERY_WITHOUT_TRANSACTION =
+ "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
}
diff --git a/android/arch/persistence/room/Transaction.java b/android/arch/persistence/room/Transaction.java
index 914e4f41..3b6ede9c 100644
--- a/android/arch/persistence/room/Transaction.java
+++ b/android/arch/persistence/room/Transaction.java
@@ -22,9 +22,10 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Marks a method in an abstract {@link Dao} class as a transaction method.
+ * Marks a method in a {@link Dao} class as a transaction method.
* <p>
- * The derived implementation of the method will execute the super method in a database transaction.
+ * When used on a non-abstract method of an abstract {@link Dao} class,
+ * 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>
@@ -44,6 +45,38 @@ import java.lang.annotation.Target;
* }
* }
* </pre>
+ * <p>
+ * When used on a {@link Query} method that has a {@code Select} statement, the generated code for
+ * the Query will be run in a transaction. There are 2 main cases where you may want to do that:
+ * <ol>
+ * <li>If the result of the query is fairly big, it is better to run it inside a transaction
+ * to receive a consistent result. Otherwise, if the query result does not fit into a single
+ * {@link android.database.CursorWindow CursorWindow}, the query result may be corrupted due to
+ * changes in the database in between cursor window swaps.
+ * <li>If the result of the query is a Pojo with {@link Relation} fields, these fields are
+ * queried separately. To receive consistent results between these queries, you probably want
+ * to run them in a single transaction.
+ * </ol>
+ * Example:
+ * <pre>
+ * class ProductWithReviews extends Product {
+ * {@literal @}Relation(parentColumn = "id", entityColumn = "productId", entity = Review.class)
+ * public List&lt;Review> reviews;
+ * }
+ * {@literal @}Dao
+ * public interface ProductDao {
+ * {@literal @}Transaction {@literal @}Query("SELECT * from products")
+ * public List&lt;ProductWithReviews> loadAll();
+ * }
+ * </pre>
+ * If the query is an async query (e.g. returns a {@link android.arch.lifecycle.LiveData LiveData}
+ * or RxJava Flowable, the transaction is properly handled when the query is run, not when the
+ * method is called.
+ * <p>
+ * Putting this annotation on an {@link Insert}, {@link Update} or {@link Delete} method has no
+ * impact because they are always run inside a transaction. Similarly, if it is annotated with
+ * {@link Query} but runs an update or delete statement, it is automatically wrapped in a
+ * transaction.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
diff --git a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
index 818c46b4..cdd464e4 100644
--- a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
+++ b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
@@ -86,6 +86,7 @@ public class RoomPagedListActivity extends AppCompatActivity {
@Override
protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
PagedList<Customer> list = mAdapter.getCurrentList();
if (list == null) {
// Can't find anything to restore
diff --git a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
index 9d402370..b5df914a 100644
--- a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
+++ b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
@@ -59,7 +59,7 @@ public interface CustomerDao {
// Keyed
- @Query("SELECT * from customer ORDER BY mLastName ASC LIMIT :limit")
+ @Query("SELECT * from customer ORDER BY mLastName DESC LIMIT :limit")
List<Customer> customerNameInitial(int limit);
@Query("SELECT * from customer WHERE mLastName < :key ORDER BY mLastName DESC LIMIT :limit")
diff --git a/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java b/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java
deleted file mode 100644
index 3cbffc8b..00000000
--- a/android/arch/persistence/room/integration/testapp/db/JDBCOpenHelper.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.db;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-
-public class JDBCOpenHelper implements SupportSQLiteOpenHelper {
- @Override
- public String getDatabaseName() {
- return null;
- }
-
- @Override
- public void setWriteAheadLoggingEnabled(boolean enabled) {
-
- }
-
- @Override
- public SupportSQLiteDatabase getWritableDatabase() {
- return null;
- }
-
- @Override
- public SupportSQLiteDatabase getReadableDatabase() {
- return null;
- }
-
- @Override
- public void close() {
-
- }
-}
diff --git a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
index 84f20ec5..33f40183 100644
--- a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
@@ -17,20 +17,17 @@
package android.arch.persistence.room.integration.testapp.test;
import static org.hamcrest.CoreMatchers.hasItem;
-import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.TaskExecutor;
+import android.arch.core.executor.testing.CountingTaskExecutorRule;
import android.arch.persistence.room.InvalidationTracker;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.integration.testapp.TestDatabase;
import android.arch.persistence.room.integration.testapp.dao.UserDao;
import android.arch.persistence.room.integration.testapp.vo.User;
import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -38,17 +35,13 @@ import android.support.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Tests invalidation tracking.
@@ -56,138 +49,97 @@ import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class InvalidationTest {
+ @Rule
+ public CountingTaskExecutorRule executorRule = new CountingTaskExecutorRule();
private UserDao mUserDao;
private TestDatabase mDb;
@Before
- public void createDb() {
+ public void createDb() throws TimeoutException, InterruptedException {
Context context = InstrumentationRegistry.getTargetContext();
mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
mUserDao = mDb.getUserDao();
- }
-
- @Before
- public void setSingleThreadedIO() {
- ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
- ExecutorService mIOExecutor = Executors.newSingleThreadExecutor();
- Handler mHandler = new Handler(Looper.getMainLooper());
-
- @Override
- public void executeOnDiskIO(Runnable runnable) {
- mIOExecutor.execute(runnable);
- }
-
- @Override
- public void postToMainThread(Runnable runnable) {
- mHandler.post(runnable);
- }
-
- @Override
- public boolean isMainThread() {
- return Thread.currentThread() == Looper.getMainLooper().getThread();
- }
- });
+ drain();
}
@After
- public void clearExecutor() {
- ArchTaskExecutor.getInstance().setDelegate(null);
+ public void closeDb() throws TimeoutException, InterruptedException {
+ mDb.close();
+ drain();
}
- private void waitUntilIOThreadIsIdle() {
- FutureTask<Void> future = new FutureTask<>(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- return null;
- }
- });
- ArchTaskExecutor.getInstance().executeOnDiskIO(future);
- //noinspection TryWithIdenticalCatches
- try {
- future.get();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } catch (ExecutionException e) {
- throw new RuntimeException(e);
- }
+ private void drain() throws TimeoutException, InterruptedException {
+ executorRule.drainTasks(1, TimeUnit.MINUTES);
}
@Test
- public void testInvalidationOnUpdate() throws InterruptedException {
+ public void testInvalidationOnUpdate() throws InterruptedException, TimeoutException {
User user = TestUtil.createUser(3);
mUserDao.insert(user);
- LatchObserver observer = new LatchObserver(1, "User");
+ LoggingObserver observer = new LoggingObserver("User");
mDb.getInvalidationTracker().addObserver(observer);
+ drain();
mUserDao.updateById(3, "foo2");
- waitUntilIOThreadIsIdle();
- assertThat(observer.await(), is(true));
+ drain();
assertThat(observer.getInvalidatedTables(), hasSize(1));
assertThat(observer.getInvalidatedTables(), hasItem("User"));
}
@Test
- public void testInvalidationOnDelete() throws InterruptedException {
+ public void testInvalidationOnDelete() throws InterruptedException, TimeoutException {
User user = TestUtil.createUser(3);
mUserDao.insert(user);
- LatchObserver observer = new LatchObserver(1, "User");
+ LoggingObserver observer = new LoggingObserver("User");
mDb.getInvalidationTracker().addObserver(observer);
+ drain();
mUserDao.delete(user);
- waitUntilIOThreadIsIdle();
- assertThat(observer.await(), is(true));
+ drain();
assertThat(observer.getInvalidatedTables(), hasSize(1));
assertThat(observer.getInvalidatedTables(), hasItem("User"));
}
@Test
- public void testInvalidationOnInsert() throws InterruptedException {
- LatchObserver observer = new LatchObserver(1, "User");
+ public void testInvalidationOnInsert() throws InterruptedException, TimeoutException {
+ LoggingObserver observer = new LoggingObserver("User");
mDb.getInvalidationTracker().addObserver(observer);
+ drain();
mUserDao.insert(TestUtil.createUser(3));
- waitUntilIOThreadIsIdle();
- assertThat(observer.await(), is(true));
+ drain();
assertThat(observer.getInvalidatedTables(), hasSize(1));
assertThat(observer.getInvalidatedTables(), hasItem("User"));
}
@Test
- public void testDontInvalidateOnLateInsert() throws InterruptedException {
- LatchObserver observer = new LatchObserver(1, "User");
+ public void testDontInvalidateOnLateInsert() throws InterruptedException, TimeoutException {
+ LoggingObserver observer = new LoggingObserver("User");
mUserDao.insert(TestUtil.createUser(3));
- waitUntilIOThreadIsIdle();
+ drain();
mDb.getInvalidationTracker().addObserver(observer);
- waitUntilIOThreadIsIdle();
- assertThat(observer.await(), is(false));
+ drain();
+ assertThat(observer.getInvalidatedTables(), nullValue());
}
@Test
- public void testMultipleTables() throws InterruptedException {
- LatchObserver observer = new LatchObserver(1, "User", "Pet");
+ public void testMultipleTables() throws InterruptedException, TimeoutException {
+ LoggingObserver observer = new LoggingObserver("User", "Pet");
mDb.getInvalidationTracker().addObserver(observer);
+ drain();
mUserDao.insert(TestUtil.createUser(3));
- waitUntilIOThreadIsIdle();
- assertThat(observer.await(), is(true));
+ drain();
assertThat(observer.getInvalidatedTables(), hasSize(1));
assertThat(observer.getInvalidatedTables(), hasItem("User"));
}
- private static class LatchObserver extends InvalidationTracker.Observer {
- CountDownLatch mLatch;
-
+ private static class LoggingObserver extends InvalidationTracker.Observer {
private Set<String> mInvalidatedTables;
- LatchObserver(int permits, String... tables) {
+ LoggingObserver(String... tables) {
super(tables);
- mLatch = new CountDownLatch(permits);
- }
-
- boolean await() throws InterruptedException {
- return mLatch.await(5, TimeUnit.SECONDS);
}
@Override
public void onInvalidated(@NonNull Set<String> tables) {
mInvalidatedTables = tables;
- mLatch.countDown();
}
Set<String> getInvalidatedTables() {
diff --git a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
index e11117e4..2735c05a 100644
--- a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
@@ -166,17 +166,13 @@ public class QueryDataSourceTest extends TestDatabaseTest {
p = dataSource.loadBefore(15, list.get(0), 10);
assertNotNull(p);
- for (User u : p) {
- list.add(0, u);
- }
+ list.addAll(0, p);
assertArrayEquals(Arrays.copyOfRange(expected, 5, 35), list.toArray());
p = dataSource.loadBefore(5, list.get(0), 10);
assertNotNull(p);
- for (User u : p) {
- list.add(0, u);
- }
+ list.addAll(0, p);
assertArrayEquals(Arrays.copyOfRange(expected, 0, 35), list.toArray());
}
diff --git a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
new file mode 100644
index 00000000..854c8627
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
@@ -0,0 +1,471 @@
+/*
+ * 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.MatcherAssert.assertThat;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.core.executor.testing.CountingTaskExecutorRule;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.Observer;
+import android.arch.paging.LivePagedListProvider;
+import android.arch.paging.PagedList;
+import android.arch.paging.TiledDataSource;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.Ignore;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.PrimaryKey;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Relation;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.RoomWarnings;
+import android.arch.persistence.room.Transaction;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import io.reactivex.observers.TestObserver;
+import io.reactivex.schedulers.Schedulers;
+import io.reactivex.subscribers.TestSubscriber;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class QueryTransactionTest {
+ @Rule
+ public CountingTaskExecutorRule countingTaskExecutorRule = new CountingTaskExecutorRule();
+ private static final AtomicInteger sStartedTransactionCount = new AtomicInteger(0);
+ private TransactionDb mDb;
+ private final boolean mUseTransactionDao;
+ private Entity1Dao mDao;
+ private final LiveDataQueryTest.TestLifecycleOwner mLifecycleOwner = new LiveDataQueryTest
+ .TestLifecycleOwner();
+
+ @NonNull
+ @Parameterized.Parameters(name = "useTransaction_{0}")
+ public static Boolean[] getParams() {
+ return new Boolean[]{false, true};
+ }
+
+ public QueryTransactionTest(boolean useTransactionDao) {
+ mUseTransactionDao = useTransactionDao;
+ }
+
+ @Before
+ public void initDb() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+ }
+ });
+
+ resetTransactionCount();
+ mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+ TransactionDb.class).build();
+ mDao = mUseTransactionDao ? mDb.transactionDao() : mDb.dao();
+ drain();
+ }
+
+ @After
+ public void closeDb() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mLifecycleOwner.handleEvent(Lifecycle.Event.ON_DESTROY);
+ }
+ });
+ drain();
+ mDb.close();
+ }
+
+ @Test
+ public void readList() {
+ mDao.insert(new Entity1(1, "foo"));
+ resetTransactionCount();
+
+ int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+ List<Entity1> allEntities = mDao.allEntities();
+ assertTransactionCount(allEntities, expectedTransactionCount);
+ }
+
+ @Test
+ public void liveData() {
+ LiveData<List<Entity1>> listLiveData = mDao.liveData();
+ observeForever(listLiveData);
+ drain();
+ assertThat(listLiveData.getValue(), is(Collections.<Entity1>emptyList()));
+
+ resetTransactionCount();
+ mDao.insert(new Entity1(1, "foo"));
+ drain();
+
+ //noinspection ConstantConditions
+ assertThat(listLiveData.getValue().size(), is(1));
+ int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
+ assertTransactionCount(listLiveData.getValue(), expectedTransactionCount);
+ }
+
+ @Test
+ public void flowable() {
+ Flowable<List<Entity1>> flowable = mDao.flowable();
+ TestSubscriber<List<Entity1>> subscriber = observe(flowable);
+ drain();
+ assertThat(subscriber.values().size(), is(1));
+
+ resetTransactionCount();
+ mDao.insert(new Entity1(1, "foo"));
+ drain();
+
+ List<Entity1> allEntities = subscriber.values().get(1);
+ assertThat(allEntities.size(), is(1));
+ int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
+ assertTransactionCount(allEntities, expectedTransactionCount);
+ }
+
+ @Test
+ public void maybe() {
+ mDao.insert(new Entity1(1, "foo"));
+ resetTransactionCount();
+
+ int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+ Maybe<List<Entity1>> listMaybe = mDao.maybe();
+ TestObserver<List<Entity1>> observer = observe(listMaybe);
+ drain();
+ List<Entity1> allEntities = observer.values().get(0);
+ assertTransactionCount(allEntities, expectedTransactionCount);
+ }
+
+ @Test
+ public void single() {
+ mDao.insert(new Entity1(1, "foo"));
+ resetTransactionCount();
+
+ int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+ Single<List<Entity1>> listMaybe = mDao.single();
+ TestObserver<List<Entity1>> observer = observe(listMaybe);
+ drain();
+ List<Entity1> allEntities = observer.values().get(0);
+ assertTransactionCount(allEntities, expectedTransactionCount);
+ }
+
+ @Test
+ public void relation() {
+ mDao.insert(new Entity1(1, "foo"));
+ mDao.insert(new Child(1, 1));
+ mDao.insert(new Child(2, 1));
+ resetTransactionCount();
+
+ List<Entity1WithChildren> result = mDao.withRelation();
+ int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+ assertTransactionCountWithChildren(result, expectedTransactionCount);
+ }
+
+ @Test
+ public void pagedList() {
+ LiveData<PagedList<Entity1>> pagedList = mDao.pagedList().create(null, 10);
+ observeForever(pagedList);
+ drain();
+ assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0));
+
+ mDao.insert(new Entity1(1, "foo"));
+ drain();
+ //noinspection ConstantConditions
+ assertThat(pagedList.getValue().size(), is(1));
+ assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 2 : 1);
+
+ mDao.insert(new Entity1(2, "bar"));
+ drain();
+ assertThat(pagedList.getValue().size(), is(2));
+ assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 4 : 2);
+ }
+
+ @Test
+ public void dataSource() {
+ mDao.insert(new Entity1(2, "bar"));
+ drain();
+ resetTransactionCount();
+ TiledDataSource<Entity1> dataSource = mDao.dataSource();
+ dataSource.loadRange(0, 10);
+ assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0));
+ }
+
+ private void assertTransactionCount(List<Entity1> allEntities, int expectedTransactionCount) {
+ assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
+ assertThat(allEntities.isEmpty(), is(false));
+ for (Entity1 entity1 : allEntities) {
+ assertThat(entity1.transactionId, is(expectedTransactionCount));
+ }
+ }
+
+ private void assertTransactionCountWithChildren(List<Entity1WithChildren> allEntities,
+ int expectedTransactionCount) {
+ assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
+ assertThat(allEntities.isEmpty(), is(false));
+ for (Entity1WithChildren entity1 : allEntities) {
+ assertThat(entity1.transactionId, is(expectedTransactionCount));
+ assertThat(entity1.children, notNullValue());
+ assertThat(entity1.children.isEmpty(), is(false));
+ for (Child child : entity1.children) {
+ assertThat(child.transactionId, is(expectedTransactionCount));
+ }
+ }
+ }
+
+ private void resetTransactionCount() {
+ sStartedTransactionCount.set(0);
+ }
+
+ private void drain() {
+ try {
+ countingTaskExecutorRule.drainTasks(30, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new AssertionError("interrupted", e);
+ } catch (TimeoutException e) {
+ throw new AssertionError("drain timed out", e);
+ }
+ }
+
+ private <T> TestSubscriber<T> observe(final Flowable<T> flowable) {
+ TestSubscriber<T> subscriber = new TestSubscriber<>();
+ flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+ .subscribeWith(subscriber);
+ return subscriber;
+ }
+
+ private <T> TestObserver<T> observe(final Maybe<T> maybe) {
+ TestObserver<T> observer = new TestObserver<>();
+ maybe.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+ .subscribeWith(observer);
+ return observer;
+ }
+
+ private <T> TestObserver<T> observe(final Single<T> single) {
+ TestObserver<T> observer = new TestObserver<>();
+ single.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+ .subscribeWith(observer);
+ return observer;
+ }
+
+ private <T> void observeForever(final LiveData<T> liveData) {
+ FutureTask<Void> futureTask = new FutureTask<>(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ liveData.observe(mLifecycleOwner, new Observer<T>() {
+ @Override
+ public void onChanged(@Nullable T t) {
+
+ }
+ });
+ return null;
+ }
+ });
+ ArchTaskExecutor.getMainThreadExecutor().execute(futureTask);
+ try {
+ futureTask.get();
+ } catch (InterruptedException e) {
+ throw new AssertionError("interrupted", e);
+ } catch (ExecutionException e) {
+ throw new AssertionError("execution error", e);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ static class Entity1WithChildren extends Entity1 {
+ @Relation(entity = Child.class, parentColumn = "id",
+ entityColumn = "entity1Id")
+ public List<Child> children;
+
+ Entity1WithChildren(int id, String value) {
+ super(id, value);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @Entity
+ static class Child {
+ @PrimaryKey(autoGenerate = true)
+ public int id;
+ public int entity1Id;
+ @Ignore
+ public final int transactionId = sStartedTransactionCount.get();
+
+ Child(int id, int entity1Id) {
+ this.id = id;
+ this.entity1Id = entity1Id;
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @Entity
+ static class Entity1 {
+ @PrimaryKey(autoGenerate = true)
+ public int id;
+ public String value;
+ @Ignore
+ public final int transactionId = sStartedTransactionCount.get();
+
+ Entity1(int id, String value) {
+ this.id = id;
+ this.value = value;
+ }
+ }
+
+ // we don't support dao inheritance for queries so for now, go with this
+ interface Entity1Dao {
+ String SELECT_ALL = "select * from Entity1";
+
+ List<Entity1> allEntities();
+
+ Flowable<List<Entity1>> flowable();
+
+ Maybe<List<Entity1>> maybe();
+
+ Single<List<Entity1>> single();
+
+ LiveData<List<Entity1>> liveData();
+
+ List<Entity1WithChildren> withRelation();
+
+ LivePagedListProvider<Integer, Entity1> pagedList();
+
+ TiledDataSource<Entity1> dataSource();
+
+ @Insert
+ void insert(Entity1 entity1);
+
+ @Insert
+ void insert(Child entity1);
+ }
+
+ @Dao
+ interface EntityDao extends Entity1Dao {
+ @Override
+ @Query(SELECT_ALL)
+ List<Entity1> allEntities();
+
+ @Override
+ @Query(SELECT_ALL)
+ Flowable<List<Entity1>> flowable();
+
+ @Override
+ @Query(SELECT_ALL)
+ LiveData<List<Entity1>> liveData();
+
+ @Override
+ @Query(SELECT_ALL)
+ Maybe<List<Entity1>> maybe();
+
+ @Override
+ @Query(SELECT_ALL)
+ Single<List<Entity1>> single();
+
+ @Override
+ @Query(SELECT_ALL)
+ @SuppressWarnings(RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION)
+ List<Entity1WithChildren> withRelation();
+
+ @Override
+ @Query(SELECT_ALL)
+ LivePagedListProvider<Integer, Entity1> pagedList();
+
+ @Override
+ @Query(SELECT_ALL)
+ TiledDataSource<Entity1> dataSource();
+ }
+
+ @Dao
+ interface TransactionDao extends Entity1Dao {
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ List<Entity1> allEntities();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ Flowable<List<Entity1>> flowable();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ LiveData<List<Entity1>> liveData();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ Maybe<List<Entity1>> maybe();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ Single<List<Entity1>> single();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ List<Entity1WithChildren> withRelation();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ LivePagedListProvider<Integer, Entity1> pagedList();
+
+ @Override
+ @Transaction
+ @Query(SELECT_ALL)
+ TiledDataSource<Entity1> dataSource();
+ }
+
+ @Database(version = 1, entities = {Entity1.class, Child.class}, exportSchema = false)
+ abstract static class TransactionDb extends RoomDatabase {
+ abstract EntityDao dao();
+
+ abstract TransactionDao transactionDao();
+
+ @Override
+ public void beginTransaction() {
+ super.beginTransaction();
+ sStartedTransactionCount.incrementAndGet();
+ }
+ }
+}
diff --git a/android/arch/persistence/room/migration/Migration.java b/android/arch/persistence/room/migration/Migration.java
index 907e624b..d69ea0dc 100644
--- a/android/arch/persistence/room/migration/Migration.java
+++ b/android/arch/persistence/room/migration/Migration.java
@@ -17,6 +17,7 @@
package android.arch.persistence.room.migration;
import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.support.annotation.NonNull;
/**
* Base class for a database migration.
@@ -58,5 +59,5 @@ public abstract class Migration {
*
* @param database The database instance
*/
- public abstract void migrate(SupportSQLiteDatabase database);
+ public abstract void migrate(@NonNull SupportSQLiteDatabase database);
}
diff --git a/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java b/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
index 1467a4f0..d72cf8cb 100644
--- a/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
+++ b/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
@@ -16,13 +16,18 @@
package android.arch.persistence.room.migration.bundle;
+import android.support.annotation.RestrictTo;
+
import com.google.gson.annotations.SerializedName;
import java.util.List;
/**
* Holds the information about a foreign key reference.
+ *
+ * @hide
*/
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class ForeignKeyBundle {
@SerializedName("table")
private String mTable;
diff --git a/android/arch/persistence/room/paging/LimitOffsetDataSource.java b/android/arch/persistence/room/paging/LimitOffsetDataSource.java
index 800514cc..2f9a8882 100644
--- a/android/arch/persistence/room/paging/LimitOffsetDataSource.java
+++ b/android/arch/persistence/room/paging/LimitOffsetDataSource.java
@@ -49,10 +49,13 @@ public abstract class LimitOffsetDataSource<T> extends TiledDataSource<T> {
private final RoomDatabase mDb;
@SuppressWarnings("FieldCanBeLocal")
private final InvalidationTracker.Observer mObserver;
+ private final boolean mInTransaction;
- protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query, String... tables) {
+ protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query,
+ boolean inTransaction, String... tables) {
mDb = db;
mSourceQuery = query;
+ mInTransaction = inTransaction;
mCountQuery = "SELECT COUNT(*) FROM ( " + mSourceQuery.getSql() + " )";
mLimitOffsetQuery = "SELECT * FROM ( " + mSourceQuery.getSql() + " ) LIMIT ? OFFSET ?";
mObserver = new InvalidationTracker.Observer(tables) {
@@ -98,13 +101,30 @@ public abstract class LimitOffsetDataSource<T> extends TiledDataSource<T> {
sqLiteQuery.copyArgumentsFrom(mSourceQuery);
sqLiteQuery.bindLong(sqLiteQuery.getArgCount() - 1, loadCount);
sqLiteQuery.bindLong(sqLiteQuery.getArgCount(), startPosition);
- Cursor cursor = mDb.query(sqLiteQuery);
-
- try {
- return convertRows(cursor);
- } finally {
- cursor.close();
- sqLiteQuery.release();
+ if (mInTransaction) {
+ mDb.beginTransaction();
+ Cursor cursor = null;
+ try {
+ cursor = mDb.query(sqLiteQuery);
+ List<T> rows = convertRows(cursor);
+ mDb.setTransactionSuccessful();
+ return rows;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ mDb.endTransaction();
+ sqLiteQuery.release();
+ }
+ } else {
+ Cursor cursor = mDb.query(sqLiteQuery);
+ //noinspection TryFinallyCanBeTryWithResources
+ try {
+ return convertRows(cursor);
+ } finally {
+ cursor.close();
+ sqLiteQuery.release();
+ }
}
}
}
diff --git a/android/arch/persistence/room/util/StringUtil.java b/android/arch/persistence/room/util/StringUtil.java
index bee05ddd..d01e3c53 100644
--- a/android/arch/persistence/room/util/StringUtil.java
+++ b/android/arch/persistence/room/util/StringUtil.java
@@ -17,6 +17,7 @@
package android.arch.persistence.room.util;
import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
import android.util.Log;
import java.util.ArrayList;
@@ -24,10 +25,14 @@ import java.util.List;
import java.util.StringTokenizer;
/**
+ * @hide
+ *
* String utilities for Room
*/
-@SuppressWarnings("WeakerAccess")
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class StringUtil {
+
+ @SuppressWarnings("unused")
public static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
* Returns a new StringBuilder to be used while producing SQL queries.
diff --git a/android/content/ContentProvider.java b/android/content/ContentProvider.java
index 5b2bf456..cdeaea3e 100644
--- a/android/content/ContentProvider.java
+++ b/android/content/ContentProvider.java
@@ -2099,8 +2099,7 @@ 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_SLICE.equals(uri.getScheme()))) {
+ && ContentResolver.SCHEME_CONTENT.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 02e70f55..9ccc552f 100644
--- a/android/content/ContentResolver.java
+++ b/android/content/ContentResolver.java
@@ -47,8 +47,6 @@ 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;
@@ -180,8 +178,6 @@ 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";
@@ -1722,36 +1718,6 @@ 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
@@ -1759,7 +1725,7 @@ public abstract class ContentResolver {
* @hide
*/
public final IContentProvider acquireProvider(Uri uri) {
- if (!SCHEME_CONTENT.equals(uri.getScheme()) && !SCHEME_SLICE.equals(uri.getScheme())) {
+ if (!SCHEME_CONTENT.equals(uri.getScheme())) {
return null;
}
final String auth = uri.getAuthority();
diff --git a/android/content/Intent.java b/android/content/Intent.java
index c9ad9519..e47de752 100644
--- a/android/content/Intent.java
+++ b/android/content/Intent.java
@@ -53,6 +53,7 @@ import android.provider.OpenableColumns;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.XmlUtils;
@@ -9371,6 +9372,57 @@ public class Intent implements Parcelable, Cloneable {
}
}
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp,
+ boolean extras, boolean clip) {
+ long token = proto.start(fieldId);
+ if (mAction != null) {
+ proto.write(IntentProto.ACTION, mAction);
+ }
+ if (mCategories != null) {
+ for (String category : mCategories) {
+ proto.write(IntentProto.CATEGORIES, category);
+ }
+ }
+ if (mData != null) {
+ proto.write(IntentProto.DATA, secure ? mData.toSafeString() : mData.toString());
+ }
+ if (mType != null) {
+ proto.write(IntentProto.TYPE, mType);
+ }
+ if (mFlags != 0) {
+ proto.write(IntentProto.FLAG, "0x" + Integer.toHexString(mFlags));
+ }
+ if (mPackage != null) {
+ proto.write(IntentProto.PACKAGE, mPackage);
+ }
+ if (comp && mComponent != null) {
+ proto.write(IntentProto.COMPONENT, mComponent.flattenToShortString());
+ }
+ if (mSourceBounds != null) {
+ proto.write(IntentProto.SOURCE_BOUNDS, mSourceBounds.toShortString());
+ }
+ if (mClipData != null) {
+ StringBuilder b = new StringBuilder();
+ if (clip) {
+ mClipData.toShortString(b);
+ } else {
+ mClipData.toShortStringShortItems(b, false);
+ }
+ proto.write(IntentProto.CLIP_DATA, b.toString());
+ }
+ if (extras && mExtras != null) {
+ proto.write(IntentProto.EXTRAS, mExtras.toShortString());
+ }
+ if (mContentUserHint != 0) {
+ proto.write(IntentProto.CONTENT_USER_HINT, mContentUserHint);
+ }
+ if (mSelector != null) {
+ proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip));
+ }
+ proto.end(token);
+ }
+
/**
* Call {@link #toUri} with 0 flags.
* @deprecated Use {@link #toUri} instead.
diff --git a/android/content/IntentFilter.java b/android/content/IntentFilter.java
index c9bce530..a957aed8 100644
--- a/android/content/IntentFilter.java
+++ b/android/content/IntentFilter.java
@@ -26,6 +26,7 @@ import android.text.TextUtils;
import android.util.AndroidException;
import android.util.Log;
import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.XmlUtils;
@@ -918,6 +919,15 @@ public class IntentFilter implements Parcelable {
dest.writeInt(mPort);
}
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ // The original host information is already contained in host and wild, no output now.
+ proto.write(AuthorityEntryProto.HOST, mHost);
+ proto.write(AuthorityEntryProto.WILD, mWild);
+ proto.write(AuthorityEntryProto.PORT, mPort);
+ proto.end(token);
+ }
+
public String getHost() {
return mOrigHost;
}
@@ -1739,6 +1749,59 @@ public class IntentFilter implements Parcelable {
}
}
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ if (mActions.size() > 0) {
+ Iterator<String> it = mActions.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.ACTIONS, it.next());
+ }
+ }
+ if (mCategories != null) {
+ Iterator<String> it = mCategories.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.CATEGORIES, it.next());
+ }
+ }
+ if (mDataSchemes != null) {
+ Iterator<String> it = mDataSchemes.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.DATA_SCHEMES, it.next());
+ }
+ }
+ if (mDataSchemeSpecificParts != null) {
+ Iterator<PatternMatcher> it = mDataSchemeSpecificParts.iterator();
+ while (it.hasNext()) {
+ it.next().writeToProto(proto, IntentFilterProto.DATA_SCHEME_SPECS);
+ }
+ }
+ if (mDataAuthorities != null) {
+ Iterator<AuthorityEntry> it = mDataAuthorities.iterator();
+ while (it.hasNext()) {
+ it.next().writeToProto(proto, IntentFilterProto.DATA_AUTHORITIES);
+ }
+ }
+ if (mDataPaths != null) {
+ Iterator<PatternMatcher> it = mDataPaths.iterator();
+ while (it.hasNext()) {
+ it.next().writeToProto(proto, IntentFilterProto.DATA_PATHS);
+ }
+ }
+ if (mDataTypes != null) {
+ Iterator<String> it = mDataTypes.iterator();
+ while (it.hasNext()) {
+ proto.write(IntentFilterProto.DATA_TYPES, it.next());
+ }
+ }
+ if (mPriority != 0 || mHasPartialTypes) {
+ proto.write(IntentFilterProto.PRIORITY, mPriority);
+ proto.write(IntentFilterProto.HAS_PARTIAL_TYPES, mHasPartialTypes);
+ }
+ proto.write(IntentFilterProto.GET_AUTO_VERIFY, getAutoVerify());
+ proto.end(token);
+ }
+
public void dump(Printer du, String prefix) {
StringBuilder sb = new StringBuilder(256);
if (mActions.size() > 0) {
diff --git a/android/content/pm/FeatureInfo.java b/android/content/pm/FeatureInfo.java
index 9ee6fa24..ff9fd8ec 100644
--- a/android/content/pm/FeatureInfo.java
+++ b/android/content/pm/FeatureInfo.java
@@ -18,6 +18,7 @@ package android.content.pm;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
/**
* Definition of a single optional hardware or software feature of an Android
@@ -113,6 +114,18 @@ public class FeatureInfo implements Parcelable {
dest.writeInt(flags);
}
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ if (name != null) {
+ proto.write(FeatureInfoProto.NAME, name);
+ }
+ proto.write(FeatureInfoProto.VERSION, version);
+ proto.write(FeatureInfoProto.GLES_VERSION, getGlEsVersion());
+ proto.write(FeatureInfoProto.FLAGS, flags);
+ proto.end(token);
+ }
+
public static final Creator<FeatureInfo> CREATOR = new Creator<FeatureInfo>() {
@Override
public FeatureInfo createFromParcel(Parcel source) {
diff --git a/android/content/pm/LauncherApps.java b/android/content/pm/LauncherApps.java
index aa9562ff..b94a410b 100644
--- a/android/content/pm/LauncherApps.java
+++ b/android/content/pm/LauncherApps.java
@@ -20,8 +20,8 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
-import android.annotation.SystemService;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
@@ -37,10 +37,10 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.graphics.drawable.AdaptiveIconDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -282,12 +282,27 @@ public class LauncherApps {
public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
/**
- * Does not retrieve CHOOSER only shortcuts.
- * TODO: Add another flag for MATCH_ALL_PINNED
+ * @hide include all pinned shortcuts by any launchers, not just by the caller,
+ * in the result.
+ * If the caller doesn't havve the {@link android.Manifest.permission#ACCESS_SHORTCUTS}
+ * permission, this flag will be ignored.
+ */
+ @TestApi
+ public static final int FLAG_MATCH_ALL_PINNED = 1 << 10;
+
+ /**
+ * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST
* @hide
*/
public static final int FLAG_MATCH_ALL_KINDS =
- FLAG_GET_DYNAMIC | FLAG_GET_PINNED | FLAG_GET_MANIFEST;
+ FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST;
+
+ /**
+ * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_ALL_PINNED
+ * @hide
+ */
+ public static final int FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED =
+ FLAG_MATCH_ALL_KINDS | FLAG_MATCH_ALL_PINNED;
/** @hide kept for unit tests */
@Deprecated
@@ -319,6 +334,7 @@ public class LauncherApps {
FLAG_MATCH_PINNED,
FLAG_MATCH_MANIFEST,
FLAG_GET_KEY_FIELDS_ONLY,
+ FLAG_MATCH_MANIFEST,
})
@Retention(RetentionPolicy.SOURCE)
public @interface QueryFlags {}
@@ -678,6 +694,21 @@ public class LauncherApps {
}
}
+ private List<ShortcutInfo> maybeUpdateDisabledMessage(List<ShortcutInfo> shortcuts) {
+ if (shortcuts == null) {
+ return null;
+ }
+ for (int i = shortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = shortcuts.get(i);
+ final String message = ShortcutInfo.getDisabledReasonForRestoreIssue(mContext,
+ si.getDisabledReason());
+ if (message != null) {
+ si.setDisabledMessage(message);
+ }
+ }
+ return shortcuts;
+ }
+
/**
* Returns {@link ShortcutInfo}s that match {@code query}.
*
@@ -698,10 +729,16 @@ public class LauncherApps {
@NonNull UserHandle user) {
logErrorForInvalidProfileAccess(user);
try {
- return mService.getShortcuts(mContext.getPackageName(),
+ // Note this is the only case we need to update the disabled message for shortcuts
+ // that weren't restored.
+ // The restore problem messages are only shown by the user, and publishers will never
+ // see them. The only other API that the launcher gets shortcuts is the shortcut
+ // changed callback, but that only returns shortcuts with the "key" information, so
+ // that won't return disabled message.
+ return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(),
query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity,
query.mQueryFlags, user)
- .getList();
+ .getList());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java
index be7f921e..143c51da 100644
--- a/android/content/pm/PackageManagerInternal.java
+++ b/android/content/pm/PackageManagerInternal.java
@@ -467,6 +467,7 @@ public abstract class PackageManagerInternal {
/** 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);
+ /** Returns a PermissionGroup. */
+ public abstract @Nullable PackageParser.PermissionGroup getPermissionGroupTEMP(
+ @NonNull String groupName);
}
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
index 6c7c8a07..ad36139a 100644
--- a/android/content/pm/PackageParser.java
+++ b/android/content/pm/PackageParser.java
@@ -3711,17 +3711,15 @@ public class PackageParser {
ai.flags |= ApplicationInfo.FLAG_IS_GAME;
}
- if (false) {
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState,
- false)) {
- ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE;
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState,
+ false)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE;
- // A heavy-weight application can not be in a custom process.
- // We can do direct compare because we intern all strings.
- if (ai.processName != null && ai.processName != ai.packageName) {
- outError[0] = "cantSaveState applications can not use custom processes";
- }
+ // A heavy-weight application can not be in a custom process.
+ // We can do direct compare because we intern all strings.
+ if (ai.processName != null && !ai.processName.equals(ai.packageName)) {
+ outError[0] = "cantSaveState applications can not use custom processes";
}
}
}
@@ -6849,6 +6847,11 @@ public class PackageParser {
dest.writeParcelable(group, flags);
}
+ /** @hide */
+ public boolean isAppOp() {
+ return info.isAppOp();
+ }
+
private Permission(Parcel in) {
super(in);
final ClassLoader boot = Object.class.getClassLoader();
diff --git a/android/content/pm/PermissionInfo.java b/android/content/pm/PermissionInfo.java
index b45c26ce..5dd7aeda 100644
--- a/android/content/pm/PermissionInfo.java
+++ b/android/content/pm/PermissionInfo.java
@@ -353,6 +353,11 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
return size;
}
+ /** @hide */
+ public boolean isAppOp() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
+ }
+
public static final Creator<PermissionInfo> CREATOR =
new Creator<PermissionInfo>() {
@Override
diff --git a/android/content/pm/ShortcutInfo.java b/android/content/pm/ShortcutInfo.java
index 6b9c7537..9ff07757 100644
--- a/android/content/pm/ShortcutInfo.java
+++ b/android/content/pm/ShortcutInfo.java
@@ -18,6 +18,7 @@ package android.content.pm;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.TaskStackBuilder;
import android.content.ComponentName;
@@ -100,6 +101,13 @@ public final class ShortcutInfo implements Parcelable {
/** @hide When this is set, the bitmap icon is waiting to be saved. */
public static final int FLAG_ICON_FILE_PENDING_SAVE = 1 << 11;
+ /**
+ * "Shadow" shortcuts are the ones that are restored, but the owner package hasn't been
+ * installed yet.
+ * @hide
+ */
+ public static final int FLAG_SHADOW = 1 << 12;
+
/** @hide */
@IntDef(flag = true,
value = {
@@ -158,6 +166,124 @@ public final class ShortcutInfo implements Parcelable {
public @interface CloneFlags {}
/**
+ * Shortcut is not disabled.
+ */
+ public static final int DISABLED_REASON_NOT_DISABLED = 0;
+
+ /**
+ * Shortcut has been disabled by the publisher app with the
+ * {@link ShortcutManager#disableShortcuts(List)} API.
+ */
+ public static final int DISABLED_REASON_BY_APP = 1;
+
+ /**
+ * Shortcut has been disabled due to changes to the publisher app. (e.g. a manifest shortcut
+ * no longer exists.)
+ */
+ public static final int DISABLED_REASON_APP_CHANGED = 2;
+
+ /**
+ * A disabled reason that's equal to or bigger than this is due to backup and restore issue.
+ * A shortcut with such a reason wil be visible to the launcher, but not to the publisher.
+ * ({@link #isVisibleToPublisher()} will be false.)
+ */
+ private static final int DISABLED_REASON_RESTORE_ISSUE_START = 100;
+
+ /**
+ * Shortcut has been restored from the previous device, but the publisher app on the current
+ * device is of a lower version. The shortcut will not be usable until the app is upgraded to
+ * the same version or higher.
+ */
+ public static final int DISABLED_REASON_VERSION_LOWER = 100;
+
+ /**
+ * Shortcut has not been restored because the publisher app does not support backup and restore.
+ */
+ public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101;
+
+ /**
+ * Shortcut has not been restored because the publisher app's signature has changed.
+ */
+ public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102;
+
+ /**
+ * Shortcut has not been restored for unknown reason.
+ */
+ public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103;
+
+ /** @hide */
+ @IntDef(value = {
+ DISABLED_REASON_NOT_DISABLED,
+ DISABLED_REASON_BY_APP,
+ DISABLED_REASON_APP_CHANGED,
+ DISABLED_REASON_VERSION_LOWER,
+ DISABLED_REASON_BACKUP_NOT_SUPPORTED,
+ DISABLED_REASON_SIGNATURE_MISMATCH,
+ DISABLED_REASON_OTHER_RESTORE_ISSUE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DisabledReason{}
+
+ /**
+ * Return a label for disabled reasons, which are *not* supposed to be shown to the user.
+ * @hide
+ */
+ public static String getDisabledReasonDebugString(@DisabledReason int disabledReason) {
+ switch (disabledReason) {
+ case DISABLED_REASON_NOT_DISABLED:
+ return "[Not disabled]";
+ case DISABLED_REASON_BY_APP:
+ return "[Disabled: by app]";
+ case DISABLED_REASON_APP_CHANGED:
+ return "[Disabled: app changed]";
+ case DISABLED_REASON_VERSION_LOWER:
+ return "[Disabled: lower version]";
+ case DISABLED_REASON_BACKUP_NOT_SUPPORTED:
+ return "[Disabled: backup not supported]";
+ case DISABLED_REASON_SIGNATURE_MISMATCH:
+ return "[Disabled: signature mismatch]";
+ case DISABLED_REASON_OTHER_RESTORE_ISSUE:
+ return "[Disabled: unknown restore issue]";
+ }
+ return "[Disabled: unknown reason:" + disabledReason + "]";
+ }
+
+ /**
+ * Return a label for a disabled reason for shortcuts that are disabled due to a backup and
+ * restore issue. If the reason is not due to backup & restore, then it'll return null.
+ *
+ * This method returns localized, user-facing strings, which will be returned by
+ * {@link #getDisabledMessage()}.
+ *
+ * @hide
+ */
+ public static String getDisabledReasonForRestoreIssue(Context context,
+ @DisabledReason int disabledReason) {
+ final Resources res = context.getResources();
+
+ switch (disabledReason) {
+ case DISABLED_REASON_VERSION_LOWER:
+ return res.getString(
+ com.android.internal.R.string.shortcut_restored_on_lower_version);
+ case DISABLED_REASON_BACKUP_NOT_SUPPORTED:
+ return res.getString(
+ com.android.internal.R.string.shortcut_restore_not_supported);
+ case DISABLED_REASON_SIGNATURE_MISMATCH:
+ return res.getString(
+ com.android.internal.R.string.shortcut_restore_signature_mismatch);
+ case DISABLED_REASON_OTHER_RESTORE_ISSUE:
+ return res.getString(
+ com.android.internal.R.string.shortcut_restore_unknown_issue);
+ }
+ return null;
+ }
+
+ /** @hide */
+ public static boolean isDisabledForRestoreIssue(@DisabledReason int disabledReason) {
+ return disabledReason >= DISABLED_REASON_RESTORE_ISSUE_START;
+ }
+
+ /**
* Shortcut category for messaging related actions, such as chat.
*/
public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -240,6 +366,11 @@ public final class ShortcutInfo implements Parcelable {
private final int mUserId;
+ /** @hide */
+ public static final int VERSION_CODE_UNKNOWN = -1;
+
+ private int mDisabledReason;
+
private ShortcutInfo(Builder b) {
mUserId = b.mContext.getUserId();
@@ -352,6 +483,7 @@ public final class ShortcutInfo implements Parcelable {
mActivity = source.mActivity;
mFlags = source.mFlags;
mLastChangedTimestamp = source.mLastChangedTimestamp;
+ mDisabledReason = source.mDisabledReason;
// Just always keep it since it's cheep.
mIconResId = source.mIconResId;
@@ -615,13 +747,23 @@ public final class ShortcutInfo implements Parcelable {
/**
* @hide
+ *
+ * @isUpdating set true if it's "update", as opposed to "replace".
*/
- public void ensureUpdatableWith(ShortcutInfo source) {
+ public void ensureUpdatableWith(ShortcutInfo source, boolean isUpdating) {
+ if (isUpdating) {
+ Preconditions.checkState(isVisibleToPublisher(),
+ "[Framework BUG] Invisible shortcuts can't be updated");
+ }
Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match");
Preconditions.checkState(mId.equals(source.mId), "ID must match");
Preconditions.checkState(mPackageName.equals(source.mPackageName),
"Package name must match");
- Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+
+ if (isVisibleToPublisher()) {
+ // Don't do this check for restore-blocked shortcuts.
+ Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+ }
}
/**
@@ -638,7 +780,7 @@ public final class ShortcutInfo implements Parcelable {
* @hide
*/
public void copyNonNullFieldsFrom(ShortcutInfo source) {
- ensureUpdatableWith(source);
+ ensureUpdatableWith(source, /*isUpdating=*/ true);
if (source.mActivity != null) {
mActivity = source.mActivity;
@@ -1169,6 +1311,19 @@ public final class ShortcutInfo implements Parcelable {
return mDisabledMessageResId;
}
+ /** @hide */
+ public void setDisabledReason(@DisabledReason int reason) {
+ mDisabledReason = reason;
+ }
+
+ /**
+ * Returns why a shortcut has been disabled.
+ */
+ @DisabledReason
+ public int getDisabledReason() {
+ return mDisabledReason;
+ }
+
/**
* Return the shortcut's categories.
*
@@ -1403,6 +1558,21 @@ public final class ShortcutInfo implements Parcelable {
return hasFlags(FLAG_IMMUTABLE);
}
+ /** @hide */
+ public boolean isDynamicVisible() {
+ return isDynamic() && isVisibleToPublisher();
+ }
+
+ /** @hide */
+ public boolean isPinnedVisible() {
+ return isPinned() && isVisibleToPublisher();
+ }
+
+ /** @hide */
+ public boolean isManifestVisible() {
+ return isDeclaredInManifest() && isVisibleToPublisher();
+ }
+
/**
* Return if a shortcut is immutable, in which case it cannot be modified with any of
* {@link ShortcutManager} APIs.
@@ -1491,6 +1661,18 @@ public final class ShortcutInfo implements Parcelable {
}
/**
+ * When the system wasn't able to restore a shortcut, it'll still be registered to the system
+ * but disabled, and such shortcuts will not be visible to the publisher. They're still visible
+ * to launchers though.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isVisibleToPublisher() {
+ return !isDisabledForRestoreIssue(mDisabledReason);
+ }
+
+ /**
* Return whether a shortcut only contains "key" information only or not. If true, only the
* following fields are available.
* <ul>
@@ -1668,6 +1850,7 @@ public final class ShortcutInfo implements Parcelable {
mFlags = source.readInt();
mIconResId = source.readInt();
mLastChangedTimestamp = source.readLong();
+ mDisabledReason = source.readInt();
if (source.readInt() == 0) {
return; // key information only.
@@ -1711,6 +1894,7 @@ public final class ShortcutInfo implements Parcelable {
dest.writeInt(mFlags);
dest.writeInt(mIconResId);
dest.writeLong(mLastChangedTimestamp);
+ dest.writeInt(mDisabledReason);
if (hasKeyFieldsOnly()) {
dest.writeInt(0);
@@ -1808,6 +1992,11 @@ public final class ShortcutInfo implements Parcelable {
sb.append(", flags=0x");
sb.append(Integer.toHexString(mFlags));
sb.append(" [");
+ if ((mFlags & FLAG_SHADOW) != 0) {
+ // Note the shadow flag isn't actually used anywhere and it's just for dumpsys, so
+ // we don't have an isXxx for this.
+ sb.append("Sdw");
+ }
if (!isEnabled()) {
sb.append("Dis");
}
@@ -1848,7 +2037,9 @@ public final class ShortcutInfo implements Parcelable {
sb.append("packageName=");
sb.append(mPackageName);
- sb.append(", activity=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("activity=");
sb.append(mActivity);
addIndentOrComma(sb, indent);
@@ -1883,6 +2074,11 @@ public final class ShortcutInfo implements Parcelable {
addIndentOrComma(sb, indent);
+ sb.append("disabledReason=");
+ sb.append(getDisabledReasonDebugString(mDisabledReason));
+
+ addIndentOrComma(sb, indent);
+
sb.append("categories=");
sb.append(mCategories);
@@ -1953,7 +2149,7 @@ public final class ShortcutInfo implements Parcelable {
CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName,
Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
long lastChangedTimestamp,
- int flags, int iconResId, String iconResName, String bitmapPath) {
+ int flags, int iconResId, String iconResName, String bitmapPath, int disabledReason) {
mUserId = userId;
mId = id;
mPackageName = packageName;
@@ -1978,5 +2174,6 @@ public final class ShortcutInfo implements Parcelable {
mIconResId = iconResId;
mIconResName = iconResName;
mBitmapPath = bitmapPath;
+ mDisabledReason = disabledReason;
}
}
diff --git a/android/content/pm/ShortcutServiceInternal.java b/android/content/pm/ShortcutServiceInternal.java
index 7b7d8ae4..7fc25d82 100644
--- a/android/content/pm/ShortcutServiceInternal.java
+++ b/android/content/pm/ShortcutServiceInternal.java
@@ -46,7 +46,7 @@ public abstract class ShortcutServiceInternal {
@NonNull String callingPackage, long changedSince,
@Nullable String packageName, @Nullable List<String> shortcutIds,
@Nullable ComponentName componentName, @ShortcutQuery.QueryFlags int flags,
- int userId);
+ int userId, int callingPid, int callingUid);
public abstract boolean
isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
@@ -58,7 +58,8 @@ public abstract class ShortcutServiceInternal {
public abstract Intent[] createShortcutIntents(
int launcherUserId, @NonNull String callingPackage,
- @NonNull String packageName, @NonNull String shortcutId, int userId);
+ @NonNull String packageName, @NonNull String shortcutId, int userId,
+ int callingPid, int callingUid);
public abstract void addListener(@NonNull ShortcutChangeListener listener);
@@ -70,7 +71,7 @@ public abstract class ShortcutServiceInternal {
@NonNull String packageName, @NonNull String shortcutId, int userId);
public abstract boolean hasShortcutHostPermission(int launcherUserId,
- @NonNull String callingPackage);
+ @NonNull String callingPackage, int callingPid, int callingUid);
public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
@NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
diff --git a/android/content/res/ResourcesImpl.java b/android/content/res/ResourcesImpl.java
index a8b8c4b5..386239cf 100644
--- a/android/content/res/ResourcesImpl.java
+++ b/android/content/res/ResourcesImpl.java
@@ -796,7 +796,7 @@ public class ResourcesImpl {
dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
is.close();
}
- } catch (Exception e) {
+ } catch (Exception | StackOverflowError e) {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
final NotFoundException rnf = new NotFoundException(
"File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
diff --git a/android/database/SQLiteDatabaseIoPerfTest.java b/android/database/SQLiteDatabaseIoPerfTest.java
new file mode 100644
index 00000000..7c5316d2
--- /dev/null
+++ b/android/database/SQLiteDatabaseIoPerfTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.database;
+
+import android.app.Activity;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Performance tests for measuring amount of data written during typical DB operations
+ *
+ * <p>To run: bit CorePerfTests:android.database.SQLiteDatabaseIoPerfTest
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SQLiteDatabaseIoPerfTest {
+ private static final String TAG = "SQLiteDatabaseIoPerfTest";
+ private static final String DB_NAME = "db_io_perftest";
+ private static final int DEFAULT_DATASET_SIZE = 500;
+
+ private Long mWriteBytes;
+
+ private SQLiteDatabase mDatabase;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContext.deleteDatabase(DB_NAME);
+ mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+ mDatabase.execSQL("CREATE TABLE T1 "
+ + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
+ }
+
+ @After
+ public void tearDown() {
+ mDatabase.close();
+ mContext.deleteDatabase(DB_NAME);
+ }
+
+ @Test
+ public void testDatabaseModifications() {
+ startMeasuringWrites();
+ ContentValues cv = new ContentValues();
+ String[] whereArg = new String[1];
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ cv.put("_ID", i);
+ cv.put("COL_A", i);
+ cv.put("COL_B", "NewValue");
+ cv.put("COL_C", 1.0);
+ assertEquals(i, mDatabase.insert("T1", null, cv));
+ }
+ cv = new ContentValues();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ cv.put("COL_B", "UpdatedValue");
+ cv.put("COL_C", 1.1);
+ whereArg[0] = String.valueOf(i);
+ assertEquals(1, mDatabase.update("T1", cv, "_ID=?", whereArg));
+ }
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ whereArg[0] = String.valueOf(i);
+ assertEquals(1, mDatabase.delete("T1", "_ID=?", whereArg));
+ }
+ // Make sure all changes are written to disk
+ mDatabase.close();
+ long bytes = endMeasuringWrites();
+ sendResults("testDatabaseModifications" , bytes);
+ }
+
+ @Test
+ public void testInsertsWithTransactions() {
+ startMeasuringWrites();
+ final int txSize = 10;
+ ContentValues cv = new ContentValues();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE * 5; i++) {
+ if (i % txSize == 0) {
+ mDatabase.beginTransaction();
+ }
+ if (i % txSize == txSize-1) {
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+
+ }
+ cv.put("_ID", i);
+ cv.put("COL_A", i);
+ cv.put("COL_B", "NewValue");
+ cv.put("COL_C", 1.0);
+ assertEquals(i, mDatabase.insert("T1", null, cv));
+ }
+ // Make sure all changes are written to disk
+ mDatabase.close();
+ long bytes = endMeasuringWrites();
+ sendResults("testInsertsWithTransactions" , bytes);
+ }
+
+ private void startMeasuringWrites() {
+ Preconditions.checkState(mWriteBytes == null, "Measurement already started");
+ mWriteBytes = getIoStats().get("write_bytes");
+ }
+
+ private long endMeasuringWrites() {
+ Preconditions.checkState(mWriteBytes != null, "Measurement wasn't started");
+ Long newWriteBytes = getIoStats().get("write_bytes");
+ return newWriteBytes - mWriteBytes;
+ }
+
+ private void sendResults(String testName, long writeBytes) {
+ Log.i(TAG, testName + " write_bytes: " + writeBytes);
+ Bundle status = new Bundle();
+ status.putLong("write_bytes", writeBytes);
+ InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status);
+ }
+
+ private static Map<String, Long> getIoStats() {
+ String ioStat = "/proc/self/io";
+ Map<String, Long> results = new ArrayMap<>();
+ try {
+ List<String> lines = Files.readAllLines(new File(ioStat).toPath());
+ for (String line : lines) {
+ line = line.trim();
+ String[] split = line.split(":");
+ if (split.length == 2) {
+ try {
+ String key = split[0].trim();
+ Long value = Long.valueOf(split[1].trim());
+ results.put(key, value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Cannot parse number from " + line);
+ }
+ } else if (line.isEmpty()) {
+ Log.e(TAG, "Cannot parse line " + line);
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Can't read: " + ioStat, e);
+ }
+ return results;
+ }
+
+}
diff --git a/android/database/SQLiteDatabasePerfTest.java b/android/database/SQLiteDatabasePerfTest.java
new file mode 100644
index 00000000..7a32c0cc
--- /dev/null
+++ b/android/database/SQLiteDatabasePerfTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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.database;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Performance tests for typical CRUD operations and loading rows into the Cursor
+ *
+ * <p>To run: bit CorePerfTests:android.database.SQLiteDatabasePerfTest
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SQLiteDatabasePerfTest {
+ // TODO b/64262688 Add Concurrency tests to compare WAL vs DELETE read/write
+ private static final String DB_NAME = "dbperftest";
+ private static final int DEFAULT_DATASET_SIZE = 1000;
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+ private SQLiteDatabase mDatabase;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContext.deleteDatabase(DB_NAME);
+ mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+ mDatabase.execSQL("CREATE TABLE T1 "
+ + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
+ mDatabase.execSQL("CREATE TABLE T2 ("
+ + "_ID INTEGER PRIMARY KEY, COL_A VARCHAR(100), T1_ID INTEGER,"
+ + "FOREIGN KEY(T1_ID) REFERENCES T1 (_ID))");
+ }
+
+ @After
+ public void tearDown() {
+ mDatabase.close();
+ mContext.deleteDatabase(DB_NAME);
+ }
+
+ @Test
+ public void testSelect() {
+ insertT1TestDataSet();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ Random rnd = new Random(0);
+ while (state.keepRunning()) {
+ int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
+ try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+ + "WHERE _ID=?", new String[]{String.valueOf(index)})) {
+ assertTrue(cursor.moveToNext());
+ assertEquals(index, cursor.getInt(0));
+ assertEquals(index, cursor.getInt(1));
+ assertEquals("T1Value" + index, cursor.getString(2));
+ assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+ }
+ }
+ }
+
+ @Test
+ public void testSelectMultipleRows() {
+ insertT1TestDataSet();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ Random rnd = new Random(0);
+ final int querySize = 50;
+ while (state.keepRunning()) {
+ int index = rnd.nextInt(DEFAULT_DATASET_SIZE - querySize - 1);
+ try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+ + "WHERE _ID BETWEEN ? and ? ORDER BY _ID",
+ new String[]{String.valueOf(index), String.valueOf(index + querySize - 1)})) {
+ int i = 0;
+ while(cursor.moveToNext()) {
+ assertEquals(index, cursor.getInt(0));
+ assertEquals(index, cursor.getInt(1));
+ assertEquals("T1Value" + index, cursor.getString(2));
+ assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+ index++;
+ i++;
+ }
+ assertEquals(querySize, i);
+ }
+ }
+ }
+
+ @Test
+ public void testInnerJoin() {
+ mDatabase.setForeignKeyConstraintsEnabled(true);
+ mDatabase.beginTransaction();
+ insertT1TestDataSet();
+ insertT2TestDataSet();
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ Random rnd = new Random(0);
+ while (state.keepRunning()) {
+ int index = rnd.nextInt(1000);
+ try (Cursor cursor = mDatabase.rawQuery(
+ "SELECT T1._ID, T1.COL_A, T1.COL_B, T1.COL_C, T2.COL_A FROM T1 "
+ + "INNER JOIN T2 on T2.T1_ID=T1._ID WHERE T1._ID = ?",
+ new String[]{String.valueOf(index)})) {
+ assertTrue(cursor.moveToNext());
+ assertEquals(index, cursor.getInt(0));
+ assertEquals(index, cursor.getInt(1));
+ assertEquals("T1Value" + index, cursor.getString(2));
+ assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+ assertEquals("T2Value" + index, cursor.getString(4));
+ }
+ }
+ }
+
+ @Test
+ public void testInsert() {
+ insertT1TestDataSet();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ ContentValues cv = new ContentValues();
+ cv.put("_ID", DEFAULT_DATASET_SIZE);
+ cv.put("COL_B", "NewValue");
+ cv.put("COL_C", 1.1);
+ String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
+ while (state.keepRunning()) {
+ assertEquals(DEFAULT_DATASET_SIZE, mDatabase.insert("T1", null, cv));
+ state.pauseTiming();
+ assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
+ state.resumeTiming();
+ }
+ }
+
+ @Test
+ public void testDelete() {
+ insertT1TestDataSet();
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
+ Object[] insertsArgs = new Object[]{DEFAULT_DATASET_SIZE, DEFAULT_DATASET_SIZE,
+ "ValueToDelete", 1.1};
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ mDatabase.execSQL("INSERT INTO T1 VALUES (?, ?, ?, ?)", insertsArgs);
+ state.resumeTiming();
+ assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
+ }
+ }
+
+ @Test
+ public void testUpdate() {
+ insertT1TestDataSet();
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ Random rnd = new Random(0);
+ int i = 0;
+ ContentValues cv = new ContentValues();
+ String[] argArray = new String[1];
+ while (state.keepRunning()) {
+ int id = rnd.nextInt(DEFAULT_DATASET_SIZE);
+ cv.put("COL_A", i);
+ cv.put("COL_B", "UpdatedValue");
+ cv.put("COL_C", i);
+ argArray[0] = String.valueOf(id);
+ assertEquals(1, mDatabase.update("T1", cv, "_ID=?", argArray));
+ i++;
+ }
+ }
+
+ private void insertT1TestDataSet() {
+ mDatabase.beginTransaction();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ mDatabase.execSQL("INSERT INTO T1 VALUES (?, ?, ?, ?)",
+ new Object[]{i, i, "T1Value" + i, i * 1.1});
+ }
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ }
+
+ private void insertT2TestDataSet() {
+ mDatabase.beginTransaction();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+ mDatabase.execSQL("INSERT INTO T2 VALUES (?, ?, ?)",
+ new Object[]{i, "T2Value" + i, i});
+ }
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ }
+}
+
diff --git a/android/graphics/Bitmap.java b/android/graphics/Bitmap.java
index 57c75490..0072012f 100644
--- a/android/graphics/Bitmap.java
+++ b/android/graphics/Bitmap.java
@@ -21,6 +21,7 @@ import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import android.annotation.WorkerThread;
import android.content.res.ResourcesImpl;
import android.os.Parcel;
import android.os.Parcelable;
@@ -1233,6 +1234,7 @@ public final class Bitmap implements Parcelable {
* @param stream The outputstream to write the compressed data.
* @return true if successfully compressed to the specified stream.
*/
+ @WorkerThread
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
checkRecycled("Can't compress a recycled bitmap");
// do explicit check before calling the native method
diff --git a/android/graphics/BitmapFactory.java b/android/graphics/BitmapFactory.java
index ffb39e33..f5bf754a 100644
--- a/android/graphics/BitmapFactory.java
+++ b/android/graphics/BitmapFactory.java
@@ -354,6 +354,7 @@ public class BitmapFactory {
* decode, in the case of which a more accurate, but slightly slower,
* IDCT method will be used instead.
*/
+ @Deprecated
public boolean inPreferQualityOverSpeed;
/**
@@ -412,6 +413,7 @@ public class BitmapFactory {
* can check, inbetween the bounds decode and the image decode, to see
* if the operation is canceled.
*/
+ @Deprecated
public boolean mCancel;
/**
@@ -426,6 +428,7 @@ public class BitmapFactory {
* or if inJustDecodeBounds is true, will set outWidth/outHeight
* to -1
*/
+ @Deprecated
public void requestCancelDecode() {
mCancel = true;
}
diff --git a/android/media/AudioAttributes.java b/android/media/AudioAttributes.java
index 26ead3d1..20405d3b 100644
--- a/android/media/AudioAttributes.java
+++ b/android/media/AudioAttributes.java
@@ -202,6 +202,22 @@ public final class AudioAttributes implements Parcelable {
* @see #SUPPRESSIBLE_USAGES
*/
public final static int SUPPRESSIBLE_NEVER = 3;
+ /**
+ * @hide
+ * Denotes a usage for alarms,
+ * will be muted when the Zen mode doesn't allow alarms
+ * @see #SUPPRESSIBLE_USAGES
+ */
+ public final static int SUPPRESSIBLE_ALARM = 4;
+ /**
+ * @hide
+ * Denotes a usage for all other sounds not caught in SUPPRESSIBLE_NOTIFICATION,
+ * SUPPRESSIBLE_CALL,SUPPRESSIBLE_NEVER or SUPPRESSIBLE_ALARM.
+ * This includes media, system, game, navigation, the assistant, and more.
+ * These will be muted when the Zen mode doesn't allow media/system/other.
+ * @see #SUPPRESSIBLE_USAGES
+ */
+ public final static int SUPPRESSIBLE_MEDIA_SYSTEM_OTHER = 5;
/**
* @hide
@@ -221,6 +237,13 @@ public final class AudioAttributes implements Parcelable {
SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_EVENT, SUPPRESSIBLE_NOTIFICATION);
SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_ACCESSIBILITY, SUPPRESSIBLE_NEVER);
SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION, SUPPRESSIBLE_NEVER);
+ SUPPRESSIBLE_USAGES.put(USAGE_ALARM, SUPPRESSIBLE_ALARM);
+ SUPPRESSIBLE_USAGES.put(USAGE_MEDIA, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_SONIFICATION, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_GAME, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
}
/**
diff --git a/android/media/MediaMetadataRetriever.java b/android/media/MediaMetadataRetriever.java
index 4ea4e381..760cc49b 100644
--- a/android/media/MediaMetadataRetriever.java
+++ b/android/media/MediaMetadataRetriever.java
@@ -395,7 +395,7 @@ public class MediaMetadataRetriever
* @see #getFrameAtTime(long, int)
*/
/* Do not change these option values without updating their counterparts
- * in include/media/stagefright/MediaSource.h!
+ * in include/media/MediaSource.h!
*/
/**
* This option is used with {@link #getFrameAtTime(long, int)} to retrieve
diff --git a/android/media/MediaRecorder.java b/android/media/MediaRecorder.java
index 59a124fa..76784904 100644
--- a/android/media/MediaRecorder.java
+++ b/android/media/MediaRecorder.java
@@ -917,7 +917,7 @@ public class MediaRecorder
*/
public void setNextOutputFile(File file) throws IOException
{
- RandomAccessFile f = new RandomAccessFile(file, "rws");
+ RandomAccessFile f = new RandomAccessFile(file, "rw");
try {
_setNextOutputFile(f.getFD());
} finally {
@@ -942,7 +942,7 @@ public class MediaRecorder
public void prepare() throws IllegalStateException, IOException
{
if (mPath != null) {
- RandomAccessFile file = new RandomAccessFile(mPath, "rws");
+ RandomAccessFile file = new RandomAccessFile(mPath, "rw");
try {
_setOutputFile(file.getFD());
} finally {
@@ -951,7 +951,7 @@ public class MediaRecorder
} else if (mFd != null) {
_setOutputFile(mFd);
} else if (mFile != null) {
- RandomAccessFile file = new RandomAccessFile(mFile, "rws");
+ RandomAccessFile file = new RandomAccessFile(mFile, "rw");
try {
_setOutputFile(file.getFD());
} finally {
diff --git a/android/media/tv/TvInputManager.java b/android/media/tv/TvInputManager.java
index d7a9edef..fd1f2cf6 100644
--- a/android/media/tv/TvInputManager.java
+++ b/android/media/tv/TvInputManager.java
@@ -2590,12 +2590,9 @@ public final class TvInputManager {
}
}
+ /** @removed */
public boolean dispatchKeyEventToHdmi(KeyEvent event) {
- try {
- return mInterface.dispatchKeyEventToHdmi(event);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
+ return false;
}
public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
diff --git a/android/net/LinkProperties.java b/android/net/LinkProperties.java
index 2c9fb23e..4e474c8e 100644
--- a/android/net/LinkProperties.java
+++ b/android/net/LinkProperties.java
@@ -683,9 +683,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv4Address() {
for (LinkAddress address : mLinkAddresses) {
- if (address.getAddress() instanceof Inet4Address) {
- return true;
- }
+ if (address.getAddress() instanceof Inet4Address) {
+ return true;
+ }
}
return false;
}
@@ -725,9 +725,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv4DefaultRoute() {
for (RouteInfo r : mRoutes) {
- if (r.isIPv4Default()) {
- return true;
- }
+ if (r.isIPv4Default()) {
+ return true;
+ }
}
return false;
}
@@ -740,9 +740,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv6DefaultRoute() {
for (RouteInfo r : mRoutes) {
- if (r.isIPv6Default()) {
- return true;
- }
+ if (r.isIPv6Default()) {
+ return true;
+ }
}
return false;
}
@@ -755,9 +755,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv4DnsServer() {
for (InetAddress ia : mDnses) {
- if (ia instanceof Inet4Address) {
- return true;
- }
+ if (ia instanceof Inet4Address) {
+ return true;
+ }
}
return false;
}
@@ -770,9 +770,9 @@ public final class LinkProperties implements Parcelable {
*/
public boolean hasIPv6DnsServer() {
for (InetAddress ia : mDnses) {
- if (ia instanceof Inet6Address) {
- return true;
- }
+ if (ia instanceof Inet6Address) {
+ return true;
+ }
}
return false;
}
diff --git a/android/net/ip/ConnectivityPacketTracker.java b/android/net/ip/ConnectivityPacketTracker.java
index 0230f36b..1925c39e 100644
--- a/android/net/ip/ConnectivityPacketTracker.java
+++ b/android/net/ip/ConnectivityPacketTracker.java
@@ -25,6 +25,7 @@ import android.os.Handler;
import android.system.ErrnoException;
import android.system.Os;
import android.system.PacketSocketAddress;
+import android.text.TextUtils;
import android.util.Log;
import android.util.LocalLog;
@@ -59,11 +60,14 @@ public class ConnectivityPacketTracker {
private static final boolean DBG = false;
private static final String MARK_START = "--- START ---";
private static final String MARK_STOP = "--- STOP ---";
+ private static final String MARK_NAMED_START = "--- START (%s) ---";
+ private static final String MARK_NAMED_STOP = "--- STOP (%s) ---";
private final String mTag;
private final LocalLog mLog;
private final BlockingSocketReader mPacketListener;
private boolean mRunning;
+ private String mDisplayName;
public ConnectivityPacketTracker(Handler h, NetworkInterface netif, LocalLog log) {
final String ifname;
@@ -85,14 +89,16 @@ public class ConnectivityPacketTracker {
mPacketListener = new PacketListener(h, ifindex, hwaddr, mtu);
}
- public void start() {
+ public void start(String displayName) {
mRunning = true;
+ mDisplayName = displayName;
mPacketListener.start();
}
public void stop() {
mPacketListener.stop();
mRunning = false;
+ mDisplayName = null;
}
private final class PacketListener extends BlockingSocketReader {
@@ -133,16 +139,19 @@ public class ConnectivityPacketTracker {
@Override
protected void onStart() {
- mLog.log(MARK_START);
+ final String msg = TextUtils.isEmpty(mDisplayName)
+ ? MARK_START
+ : String.format(MARK_NAMED_START, mDisplayName);
+ mLog.log(msg);
}
@Override
protected void onStop() {
- if (mRunning) {
- mLog.log(MARK_STOP);
- } else {
- mLog.log(MARK_STOP + " (packet listener stopped unexpectedly)");
- }
+ String msg = TextUtils.isEmpty(mDisplayName)
+ ? MARK_STOP
+ : String.format(MARK_NAMED_STOP, mDisplayName);
+ if (!mRunning) msg += " (packet listener stopped unexpectedly)";
+ mLog.log(msg);
}
@Override
diff --git a/android/net/ip/IpManager.java b/android/net/ip/IpManager.java
index bc07b810..e33f6c99 100644
--- a/android/net/ip/IpManager.java
+++ b/android/net/ip/IpManager.java
@@ -26,6 +26,7 @@ import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties.ProvisioningChange;
import android.net.LinkProperties;
+import android.net.Network;
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.StaticIpConfiguration;
@@ -348,6 +349,16 @@ public class IpManager extends StateMachine {
return this;
}
+ public Builder withNetwork(Network network) {
+ mConfig.mNetwork = network;
+ return this;
+ }
+
+ public Builder withDisplayName(String displayName) {
+ mConfig.mDisplayName = displayName;
+ return this;
+ }
+
public ProvisioningConfiguration build() {
return new ProvisioningConfiguration(mConfig);
}
@@ -362,6 +373,8 @@ public class IpManager extends StateMachine {
/* package */ ApfCapabilities mApfCapabilities;
/* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
/* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ /* package */ Network mNetwork = null;
+ /* package */ String mDisplayName = null;
public ProvisioningConfiguration() {} // used by Builder
@@ -374,6 +387,9 @@ public class IpManager extends StateMachine {
mStaticIpConfig = other.mStaticIpConfig;
mApfCapabilities = other.mApfCapabilities;
mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
+ mIPv6AddrGenMode = other.mIPv6AddrGenMode;
+ mNetwork = other.mNetwork;
+ mDisplayName = other.mDisplayName;
}
@Override
@@ -388,6 +404,8 @@ public class IpManager extends StateMachine {
.add("mApfCapabilities: " + mApfCapabilities)
.add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
.add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
+ .add("mNetwork: " + mNetwork)
+ .add("mDisplayName: " + mDisplayName)
.toString();
}
@@ -1441,10 +1459,10 @@ public class IpManager extends StateMachine {
@Override
public void enter() {
// Get the Configuration for ApfFilter from Context
- boolean filter802_3Frames =
+ final boolean filter802_3Frames =
mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
- int[] ethTypeBlackList = mContext.getResources().getIntArray(
+ final int[] ethTypeBlackList = mContext.getResources().getIntArray(
R.array.config_apfEthTypeBlackList);
mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
@@ -1456,7 +1474,7 @@ public class IpManager extends StateMachine {
}
mPacketTracker = createPacketTracker();
- if (mPacketTracker != null) mPacketTracker.start();
+ if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
if (mConfiguration.mEnableIPv6 && !startIPv6()) {
doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
@@ -1470,7 +1488,7 @@ public class IpManager extends StateMachine {
return;
}
- InitialConfiguration config = mConfiguration.mInitialConfig;
+ final InitialConfiguration config = mConfiguration.mInitialConfig;
if ((config != null) && !applyInitialConfig(config)) {
// TODO introduce a new IpManagerEvent constant to distinguish this error case.
doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
diff --git a/android/net/util/SharedLog.java b/android/net/util/SharedLog.java
index 343d237f..bbd3d13e 100644
--- a/android/net/util/SharedLog.java
+++ b/android/net/util/SharedLog.java
@@ -106,6 +106,10 @@ public class SharedLog {
record(Category.NONE, msg);
}
+ public void logf(String fmt, Object... args) {
+ log(String.format(fmt, args));
+ }
+
public void mark(String msg) {
record(Category.MARK, msg);
}
diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java
index 98819279..59956964 100644
--- a/android/os/BatteryStats.java
+++ b/android/os/BatteryStats.java
@@ -1911,6 +1911,13 @@ public abstract class BatteryStats implements Parcelable {
long elapsedRealtimeUs, int which);
/**
+ * Returns the {@link Timer} object that tracks the given screen brightness.
+ *
+ * {@hide}
+ */
+ public abstract Timer getScreenBrightnessTimer(int brightnessBin);
+
+ /**
* Returns the time in microseconds that power save mode has been enabled while the device was
* running on battery.
*
@@ -2019,6 +2026,14 @@ public abstract class BatteryStats implements Parcelable {
long elapsedRealtimeUs, int which);
/**
+ * Returns the {@link Timer} object that tracks how much the phone has been trying to
+ * acquire a signal.
+ *
+ * {@hide}
+ */
+ public abstract Timer getPhoneSignalScanningTimer();
+
+ /**
* Returns the number of times the phone has entered the given signal strength.
*
* {@hide}
@@ -2026,6 +2041,12 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getPhoneSignalStrengthCount(int strengthBin, int which);
/**
+ * Return the {@link Timer} object used to track the given signal strength's duration and
+ * counts.
+ */
+ protected abstract Timer getPhoneSignalStrengthTimer(int strengthBin);
+
+ /**
* Returns the time in microseconds that the mobile network has been active
* (in a high power state).
*
@@ -2108,6 +2129,11 @@ public abstract class BatteryStats implements Parcelable {
*/
public abstract int getPhoneDataConnectionCount(int dataType, int which);
+ /**
+ * Returns the {@link Timer} object that tracks the phone's data connection type stats.
+ */
+ public abstract Timer getPhoneDataConnectionTimer(int dataType);
+
public static final int WIFI_SUPPL_STATE_INVALID = 0;
public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1;
public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2;
@@ -2267,6 +2293,13 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getWifiStateCount(int wifiState, int which);
/**
+ * Returns the {@link Timer} object that tracks the given WiFi state.
+ *
+ * {@hide}
+ */
+ public abstract Timer getWifiStateTimer(int wifiState);
+
+ /**
* Returns the time in microseconds that the wifi supplicant has been
* in a given state.
*
@@ -2282,6 +2315,13 @@ public abstract class BatteryStats implements Parcelable {
*/
public abstract int getWifiSupplStateCount(int state, int which);
+ /**
+ * Returns the {@link Timer} object that tracks the given wifi supplicant state.
+ *
+ * {@hide}
+ */
+ public abstract Timer getWifiSupplStateTimer(int state);
+
public static final int NUM_WIFI_SIGNAL_STRENGTH_BINS = 5;
/**
@@ -2301,6 +2341,13 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getWifiSignalStrengthCount(int strengthBin, int which);
/**
+ * Returns the {@link Timer} object that tracks the given WIFI signal strength.
+ *
+ * {@hide}
+ */
+ public abstract Timer getWifiSignalStrengthTimer(int strengthBin);
+
+ /**
* Returns the time in microseconds that the flashlight has been on while the device was
* running on battery.
*
@@ -2487,13 +2534,13 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getDischargeAmountScreenOffSinceCharge();
/**
- * Get the amount the battery has discharged while the screen was doze,
+ * Get the amount the battery has discharged while the screen was dozing,
* since the last time power was unplugged.
*/
public abstract int getDischargeAmountScreenDoze();
/**
- * Get the amount the battery has discharged while the screen was doze,
+ * Get the amount the battery has discharged while the screen was dozing,
* since the last time the device was charged.
*/
public abstract int getDischargeAmountScreenDozeSinceCharge();
@@ -2626,20 +2673,20 @@ public abstract class BatteryStats implements Parcelable {
* micro-Ampere-hours. This will be non-zero only if the device's battery has
* a coulomb counter.
*/
- public abstract long getMahDischargeScreenOff(int which);
+ public abstract long getUahDischargeScreenOff(int which);
/**
* 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 long getMahDischargeScreenDoze(int which);
+ public abstract long getUahDischargeScreenDoze(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);
+ public abstract long getUahDischarge(int which);
/**
* Returns the estimated real battery capacity, which may be less than the capacity
@@ -2984,7 +3031,7 @@ public abstract class BatteryStats implements Parcelable {
final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
/ 1000;
final int count = timer.getCountLocked(which);
- if (totalTime != 0) {
+ if (totalTime != 0 || count != 0) {
dumpLine(pw, uid, category, type, totalTime, count);
}
}
@@ -3000,12 +3047,12 @@ public abstract class BatteryStats implements Parcelable {
* @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) {
+ Timer timer, long rawRealtimeUs, int which) {
if (timer == null) {
return;
}
// Convert from microseconds to milliseconds with rounding
- final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+ final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtimeUs, which) + 500) / 1000;
final int count = timer.getCountLocked(which);
if (totalTimeMs != 0 || count != 0) {
final long token = proto.start(fieldId);
@@ -3114,71 +3161,104 @@ public abstract class BatteryStats implements Parcelable {
final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(which);
final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(which);
final long powerDrainMaMs = counter.getPowerCounter().getCountLocked(which);
+ // Battery real time
+ final long totalControllerActivityTimeMs
+ = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which) / 1000;
long totalTxTimeMs = 0;
for (LongCounter txState : counter.getTxTimeCounters()) {
totalTxTimeMs += txState.getCountLocked(which);
}
+ final long sleepTimeMs
+ = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + totalTxTimeMs);
- final long totalTimeMs = idleTimeMs + rxTimeMs + totalTxTimeMs;
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" ");
+ sb.append(controllerName);
+ sb.append(" Sleep time: ");
+ formatTimeMs(sb, sleepTimeMs);
+ sb.append("(");
+ sb.append(formatRatioLocked(sleepTimeMs, totalControllerActivityTimeMs));
+ sb.append(")");
+ pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" ");
+ sb.append(" ");
sb.append(controllerName);
sb.append(" Idle time: ");
formatTimeMs(sb, idleTimeMs);
sb.append("(");
- sb.append(formatRatioLocked(idleTimeMs, totalTimeMs));
+ sb.append(formatRatioLocked(idleTimeMs, totalControllerActivityTimeMs));
sb.append(")");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" ");
+ sb.append(" ");
sb.append(controllerName);
sb.append(" Rx time: ");
formatTimeMs(sb, rxTimeMs);
sb.append("(");
- sb.append(formatRatioLocked(rxTimeMs, totalTimeMs));
+ sb.append(formatRatioLocked(rxTimeMs, totalControllerActivityTimeMs));
sb.append(")");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" ");
+ sb.append(" ");
sb.append(controllerName);
sb.append(" Tx time: ");
- formatTimeMs(sb, totalTxTimeMs);
- sb.append("(");
- sb.append(formatRatioLocked(totalTxTimeMs, totalTimeMs));
- sb.append(")");
- pw.println(sb.toString());
- final int numTxLvls = counter.getTxTimeCounters().length;
+ String [] powerLevel;
+ switch(controllerName) {
+ case "Cellular":
+ powerLevel = new String[] {
+ " less than 0dBm: ",
+ " 0dBm to 8dBm: ",
+ " 8dBm to 15dBm: ",
+ " 15dBm to 20dBm: ",
+ " above 20dBm: "};
+ break;
+ default:
+ powerLevel = new String[] {"[0]", "[1]", "[2]", "[3]", "[4]"};
+ break;
+ }
+ final int numTxLvls = Math.min(counter.getTxTimeCounters().length, powerLevel.length);
if (numTxLvls > 1) {
+ pw.println(sb.toString());
for (int lvl = 0; lvl < numTxLvls; lvl++) {
final long txLvlTimeMs = counter.getTxTimeCounters()[lvl].getCountLocked(which);
sb.setLength(0);
sb.append(prefix);
- sb.append(" [");
- sb.append(lvl);
- sb.append("] ");
+ sb.append(" ");
+ sb.append(powerLevel[lvl]);
+ sb.append(" ");
formatTimeMs(sb, txLvlTimeMs);
sb.append("(");
- sb.append(formatRatioLocked(txLvlTimeMs, totalTxTimeMs));
+ sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs));
sb.append(")");
pw.println(sb.toString());
}
+ } else {
+ final long txLvlTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which);
+ formatTimeMs(sb, txLvlTimeMs);
+ sb.append("(");
+ sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs));
+ sb.append(")");
+ pw.println(sb.toString());
}
- sb.setLength(0);
- sb.append(prefix);
- sb.append(" ");
- sb.append(controllerName);
- sb.append(" Power drain: ").append(
+ if (powerDrainMaMs > 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" ");
+ sb.append(controllerName);
+ sb.append(" Battery drain: ").append(
BatteryStatsHelper.makemAh(powerDrainMaMs / (double) (1000*60*60)));
- sb.append("mAh");
- pw.println(sb.toString());
+ sb.append("mAh");
+ pw.println(sb.toString());
+ }
}
/**
@@ -3191,13 +3271,13 @@ public abstract class BatteryStats implements Parcelable {
/**
* Checkin server version of dump to produce more compact, computer-readable log.
*
- * NOTE: all times are expressed in 'ms'.
+ * NOTE: all times are expressed in microseconds, unless specified otherwise.
*/
public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid,
boolean wifiOnly) {
final long rawUptime = SystemClock.uptimeMillis() * 1000;
- final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
- final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+ final long rawRealtimeMs = SystemClock.elapsedRealtime();
+ final long rawRealtime = rawRealtimeMs * 1000;
final long batteryUptime = getBatteryUptime(rawUptime);
final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
@@ -3220,9 +3300,9 @@ public abstract class BatteryStats implements Parcelable {
rawRealtime, which);
final int connChanges = getNumConnectivityChange(which);
final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
- final long dischargeCount = getMahDischarge(which);
- final long dischargeScreenOffCount = getMahDischargeScreenOff(which);
- final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which);
+ final long dischargeCount = getUahDischarge(which);
+ final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
+ final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
final StringBuilder sb = new StringBuilder(128);
@@ -3460,9 +3540,9 @@ public abstract class BatteryStats implements Parcelable {
BatteryStatsHelper.makemAh(helper.getComputedPower()),
BatteryStatsHelper.makemAh(helper.getMinDrainedPower()),
BatteryStatsHelper.makemAh(helper.getMaxDrainedPower()));
+ int uid = 0;
for (int i=0; i<sippers.size(); i++) {
final BatterySipper bs = sippers.get(i);
- int uid = 0;
String label;
switch (bs.drainType) {
case IDLE:
@@ -3503,6 +3583,9 @@ public abstract class BatteryStats implements Parcelable {
case CAMERA:
label = "camera";
break;
+ case MEMORY:
+ label = "memory";
+ break;
default:
label = "???";
}
@@ -3523,6 +3606,7 @@ public abstract class BatteryStats implements Parcelable {
dumpLine(pw, 0 /* uid */, category, GLOBAL_CPU_FREQ_DATA, sb.toString());
}
+ // Dump stats per UID.
for (int iu = 0; iu < NU; iu++) {
final int uid = uidStats.keyAt(iu);
if (reqUid >= 0 && uid != reqUid) {
@@ -4020,7 +4104,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- final long dischargeCount = getMahDischarge(which);
+ final long dischargeCount = getUahDischarge(which);
if (dischargeCount >= 0) {
sb.setLength(0);
sb.append(prefix);
@@ -4030,7 +4114,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- final long dischargeScreenOffCount = getMahDischargeScreenOff(which);
+ final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
if (dischargeScreenOffCount >= 0) {
sb.setLength(0);
sb.append(prefix);
@@ -4040,7 +4124,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which);
+ final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
if (dischargeScreenDozeCount >= 0) {
sb.setLength(0);
sb.append(prefix);
@@ -4246,51 +4330,50 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
+ pw.println("");
pw.print(prefix);
- pw.print(" Mobile total received: "); pw.print(formatBytesLocked(mobileRxTotalBytes));
- pw.print(", sent: "); pw.print(formatBytesLocked(mobileTxTotalBytes));
- pw.print(" (packets received "); pw.print(mobileRxTotalPackets);
- pw.print(", sent "); pw.print(mobileTxTotalPackets); pw.println(")");
sb.setLength(0);
sb.append(prefix);
- sb.append(" Phone signal levels:");
- didOne = false;
- for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
- final long time = getPhoneSignalStrengthTime(i, rawRealtime, which);
- if (time == 0) {
- continue;
- }
- sb.append("\n ");
- sb.append(prefix);
- didOne = true;
- sb.append(SignalStrength.SIGNAL_STRENGTH_NAMES[i]);
- sb.append(" ");
- formatTimeMs(sb, time/1000);
- sb.append("(");
- sb.append(formatRatioLocked(time, whichBatteryRealtime));
- sb.append(") ");
- sb.append(getPhoneSignalStrengthCount(i, which));
- sb.append("x");
- }
- if (!didOne) sb.append(" (no activity)");
+ sb.append(" CONNECTIVITY POWER SUMMARY START");
+ pw.println(sb.toString());
+
+ pw.print(prefix);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Logging duration for connectivity statistics: ");
+ formatTimeMs(sb, whichBatteryRealtime / 1000);
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Cellular Statistics:");
pw.println(sb.toString());
+ pw.print(prefix);
sb.setLength(0);
sb.append(prefix);
- sb.append(" Signal scanning time: ");
- formatTimeMsNoSpace(sb, getPhoneSignalScanningTime(rawRealtime, which) / 1000);
+ sb.append(" Cellular kernel active time: ");
+ final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which);
+ formatTimeMs(sb, mobileActiveTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime));
+ sb.append(")");
pw.println(sb.toString());
+ pw.print(" Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes));
+ pw.print(" Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes));
+ pw.print(" Cellular packets received: "); pw.println(mobileRxTotalPackets);
+ pw.print(" Cellular packets sent: "); pw.println(mobileTxTotalPackets);
+
sb.setLength(0);
sb.append(prefix);
- sb.append(" Radio types:");
+ sb.append(" Cellular Radio Access Technology:");
didOne = false;
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
final long time = getPhoneDataConnectionTime(i, rawRealtime, which);
if (time == 0) {
continue;
}
- sb.append("\n ");
+ sb.append("\n ");
sb.append(prefix);
didOne = true;
sb.append(DATA_CONNECTION_NAMES[i]);
@@ -4299,73 +4382,64 @@ public abstract class BatteryStats implements Parcelable {
sb.append("(");
sb.append(formatRatioLocked(time, whichBatteryRealtime));
sb.append(") ");
- sb.append(getPhoneDataConnectionCount(i, which));
- sb.append("x");
}
if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" Mobile radio active time: ");
- final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which);
- formatTimeMs(sb, mobileActiveTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime));
- sb.append(") "); sb.append(getMobileRadioActiveCount(which));
- sb.append("x");
- pw.println(sb.toString());
-
- final long mobileActiveUnknownTime = getMobileRadioActiveUnknownTime(which);
- if (mobileActiveUnknownTime != 0) {
- sb.setLength(0);
- sb.append(prefix);
- sb.append(" Mobile radio active unknown time: ");
- formatTimeMs(sb, mobileActiveUnknownTime / 1000);
- sb.append("(");
- sb.append(formatRatioLocked(mobileActiveUnknownTime, whichBatteryRealtime));
- sb.append(") "); sb.append(getMobileRadioActiveUnknownCount(which));
- sb.append("x");
- pw.println(sb.toString());
- }
-
- final long mobileActiveAdjustedTime = getMobileRadioActiveAdjustedTime(which);
- if (mobileActiveAdjustedTime != 0) {
- sb.setLength(0);
+ sb.append(" Cellular Rx signal strength (RSRP):");
+ final String[] cellularRxSignalStrengthDescription = new String[]{
+ "very poor (less than -128dBm): ",
+ "poor (-128dBm to -118dBm): ",
+ "moderate (-118dBm to -108dBm): ",
+ "good (-108dBm to -98dBm): ",
+ "great (greater than -98dBm): "};
+ didOne = false;
+ final int numCellularRxBins = Math.min(SignalStrength.NUM_SIGNAL_STRENGTH_BINS,
+ cellularRxSignalStrengthDescription.length);
+ for (int i=0; i<numCellularRxBins; i++) {
+ final long time = getPhoneSignalStrengthTime(i, rawRealtime, which);
+ if (time == 0) {
+ continue;
+ }
+ sb.append("\n ");
sb.append(prefix);
- sb.append(" Mobile radio active adjusted time: ");
- formatTimeMs(sb, mobileActiveAdjustedTime / 1000);
+ didOne = true;
+ sb.append(cellularRxSignalStrengthDescription[i]);
+ sb.append(" ");
+ formatTimeMs(sb, time/1000);
sb.append("(");
- sb.append(formatRatioLocked(mobileActiveAdjustedTime, whichBatteryRealtime));
- sb.append(")");
- pw.println(sb.toString());
+ sb.append(formatRatioLocked(time, whichBatteryRealtime));
+ sb.append(") ");
}
+ if (!didOne) sb.append(" (no activity)");
+ pw.println(sb.toString());
- printControllerActivity(pw, sb, prefix, "Radio", getModemControllerActivity(), which);
+ printControllerActivity(pw, sb, prefix, "Cellular",
+ getModemControllerActivity(), which);
pw.print(prefix);
- pw.print(" Wi-Fi total received: "); pw.print(formatBytesLocked(wifiRxTotalBytes));
- pw.print(", sent: "); pw.print(formatBytesLocked(wifiTxTotalBytes));
- pw.print(" (packets received "); pw.print(wifiRxTotalPackets);
- pw.print(", sent "); pw.print(wifiTxTotalPackets); pw.println(")");
sb.setLength(0);
sb.append(prefix);
- sb.append(" Wifi on: "); formatTimeMs(sb, wifiOnTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(wifiOnTime, whichBatteryRealtime));
- sb.append("), Wifi running: "); formatTimeMs(sb, wifiRunningTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(wifiRunningTime, whichBatteryRealtime));
- sb.append(")");
+ sb.append(" Wifi Statistics:");
pw.println(sb.toString());
+ pw.print(" Wifi data received: "); pw.println(formatBytesLocked(wifiRxTotalBytes));
+ pw.print(" Wifi data sent: "); pw.println(formatBytesLocked(wifiTxTotalBytes));
+ pw.print(" Wifi packets received: "); pw.println(wifiRxTotalPackets);
+ pw.print(" Wifi packets sent: "); pw.println(wifiTxTotalPackets);
+
sb.setLength(0);
sb.append(prefix);
- sb.append(" Wifi states:");
+ sb.append(" Wifi states:");
didOne = false;
for (int i=0; i<NUM_WIFI_STATES; i++) {
final long time = getWifiStateTime(i, rawRealtime, which);
if (time == 0) {
continue;
}
- sb.append("\n ");
+ sb.append("\n ");
didOne = true;
sb.append(WIFI_STATE_NAMES[i]);
sb.append(" ");
@@ -4373,22 +4447,20 @@ public abstract class BatteryStats implements Parcelable {
sb.append("(");
sb.append(formatRatioLocked(time, whichBatteryRealtime));
sb.append(") ");
- sb.append(getWifiStateCount(i, which));
- sb.append("x");
}
if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" Wifi supplicant states:");
+ sb.append(" Wifi supplicant states:");
didOne = false;
for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
final long time = getWifiSupplStateTime(i, rawRealtime, which);
if (time == 0) {
continue;
}
- sb.append("\n ");
+ sb.append("\n ");
didOne = true;
sb.append(WIFI_SUPPL_STATE_NAMES[i]);
sb.append(" ");
@@ -4396,17 +4468,23 @@ public abstract class BatteryStats implements Parcelable {
sb.append("(");
sb.append(formatRatioLocked(time, whichBatteryRealtime));
sb.append(") ");
- sb.append(getWifiSupplStateCount(i, which));
- sb.append("x");
}
if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" Wifi signal levels:");
+ sb.append(" Wifi Rx signal strength (RSSI):");
+ final String[] wifiRxSignalStrengthDescription = new String[]{
+ "very poor (less than -88.75dBm): ",
+ "poor (-88.75 to -77.5dBm): ",
+ "moderate (-77.5dBm to -66.25dBm): ",
+ "good (-66.25dBm to -55dBm): ",
+ "great (greater than -55dBm): "};
didOne = false;
- for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+ final int numWifiRxBins = Math.min(NUM_WIFI_SIGNAL_STRENGTH_BINS,
+ wifiRxSignalStrengthDescription.length);
+ for (int i=0; i<numWifiRxBins; i++) {
final long time = getWifiSignalStrengthTime(i, rawRealtime, which);
if (time == 0) {
continue;
@@ -4414,15 +4492,12 @@ public abstract class BatteryStats implements Parcelable {
sb.append("\n ");
sb.append(prefix);
didOne = true;
- sb.append("level(");
- sb.append(i);
- sb.append(") ");
+ sb.append(" ");
+ sb.append(wifiRxSignalStrengthDescription[i]);
formatTimeMs(sb, time/1000);
sb.append("(");
sb.append(formatRatioLocked(time, whichBatteryRealtime));
sb.append(") ");
- sb.append(getWifiSignalStrengthCount(i, which));
- sb.append("x");
}
if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
@@ -4430,6 +4505,13 @@ public abstract class BatteryStats implements Parcelable {
printControllerActivity(pw, sb, prefix, "WiFi", getWifiControllerActivity(), which);
pw.print(prefix);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" CONNECTIVITY POWER SUMMARY END");
+ pw.println(sb.toString());
+ pw.println("");
+
+ pw.print(prefix);
pw.print(" Bluetooth total received: "); pw.print(formatBytesLocked(btRxTotalBytes));
pw.print(", sent: "); pw.println(formatBytesLocked(btTxTotalBytes));
@@ -6038,6 +6120,61 @@ public abstract class BatteryStats implements Parcelable {
return true;
}
+ private static void dumpDurationSteps(ProtoOutputStream proto, long fieldId,
+ LevelStepTracker steps) {
+ if (steps == null) {
+ return;
+ }
+ int count = steps.mNumStepDurations;
+ long token;
+ for (int i = 0; i < count; ++i) {
+ token = proto.start(fieldId);
+ proto.write(SystemProto.BatteryLevelStep.DURATION_MS, steps.getDurationAt(i));
+ proto.write(SystemProto.BatteryLevelStep.LEVEL, steps.getLevelAt(i));
+
+ final long initMode = steps.getInitModeAt(i);
+ final long modMode = steps.getModModeAt(i);
+
+ int ds = SystemProto.BatteryLevelStep.DS_MIXED;
+ if ((modMode & STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+ switch ((int) (initMode & STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+ case Display.STATE_OFF:
+ ds = SystemProto.BatteryLevelStep.DS_OFF;
+ break;
+ case Display.STATE_ON:
+ ds = SystemProto.BatteryLevelStep.DS_ON;
+ break;
+ case Display.STATE_DOZE:
+ ds = SystemProto.BatteryLevelStep.DS_DOZE;
+ break;
+ case Display.STATE_DOZE_SUSPEND:
+ ds = SystemProto.BatteryLevelStep.DS_DOZE_SUSPEND;
+ break;
+ default:
+ ds = SystemProto.BatteryLevelStep.DS_ERROR;
+ break;
+ }
+ }
+ proto.write(SystemProto.BatteryLevelStep.DISPLAY_STATE, ds);
+
+ int psm = SystemProto.BatteryLevelStep.PSM_MIXED;
+ if ((modMode & STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+ psm = (initMode & STEP_LEVEL_MODE_POWER_SAVE) != 0
+ ? SystemProto.BatteryLevelStep.PSM_ON : SystemProto.BatteryLevelStep.PSM_OFF;
+ }
+ proto.write(SystemProto.BatteryLevelStep.POWER_SAVE_MODE, psm);
+
+ int im = SystemProto.BatteryLevelStep.IM_MIXED;
+ if ((modMode & STEP_LEVEL_MODE_DEVICE_IDLE) == 0) {
+ im = (initMode & STEP_LEVEL_MODE_DEVICE_IDLE) != 0
+ ? SystemProto.BatteryLevelStep.IM_ON : SystemProto.BatteryLevelStep.IM_OFF;
+ }
+ proto.write(SystemProto.BatteryLevelStep.IDLE_MODE, im);
+
+ proto.end(token);
+ }
+ }
+
public static final int DUMP_CHARGED_ONLY = 1<<1;
public static final int DUMP_DAILY_ONLY = 1<<2;
public static final int DUMP_HISTORY_ONLY = 1<<3;
@@ -6463,7 +6600,7 @@ public abstract class BatteryStats implements Parcelable {
}
}
- /** Dump batterystats data to a proto. @hide */
+ /** Dump #STATS_SINCE_CHARGED 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);
@@ -6485,10 +6622,376 @@ public abstract class BatteryStats implements Parcelable {
if ((flags & (DUMP_HISTORY_ONLY | DUMP_DAILY_ONLY)) == 0) {
// TODO: implement dumpProtoAppsLocked(proto, apps);
- // TODO: implement dumpProtoSystemLocked(proto);
+ dumpProtoSystemLocked(context, proto, (flags & DUMP_DEVICE_WIFI_ONLY) != 0);
}
proto.end(bToken);
proto.flush();
}
+
+ private void dumpProtoSystemLocked(Context context, ProtoOutputStream proto, boolean wifiOnly) {
+ final long sToken = proto.start(BatteryStatsProto.SYSTEM);
+ final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
+ final long rawRealtimeMs = SystemClock.elapsedRealtime();
+ final long rawRealtimeUs = rawRealtimeMs * 1000;
+ final int which = STATS_SINCE_CHARGED;
+
+ // Battery data (BATTERY_DATA)
+ long token = proto.start(SystemProto.BATTERY);
+ proto.write(SystemProto.Battery.START_CLOCK_TIME_MS, getStartClockTime());
+ proto.write(SystemProto.Battery.START_COUNT, getStartCount());
+ proto.write(SystemProto.Battery.TOTAL_REALTIME_MS,
+ computeRealtime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.TOTAL_UPTIME_MS,
+ computeUptime(rawUptimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.BATTERY_REALTIME_MS,
+ computeBatteryRealtime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.BATTERY_UPTIME_MS,
+ computeBatteryUptime(rawUptimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.SCREEN_OFF_REALTIME_MS,
+ computeBatteryScreenOffRealtime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.SCREEN_OFF_UPTIME_MS,
+ computeBatteryScreenOffUptime(rawUptimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.SCREEN_DOZE_DURATION_MS,
+ getScreenDozeTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Battery.ESTIMATED_BATTERY_CAPACITY_MAH,
+ getEstimatedBatteryCapacity());
+ proto.write(SystemProto.Battery.MIN_LEARNED_BATTERY_CAPACITY_UAH,
+ getMinLearnedBatteryCapacity());
+ proto.write(SystemProto.Battery.MAX_LEARNED_BATTERY_CAPACITY_UAH,
+ getMaxLearnedBatteryCapacity());
+ proto.end(token);
+
+ // Battery discharge (BATTERY_DISCHARGE_DATA)
+ token = proto.start(SystemProto.BATTERY_DISCHARGE);
+ proto.write(SystemProto.BatteryDischarge.LOWER_BOUND_SINCE_CHARGE,
+ getLowDischargeAmountSinceCharge());
+ proto.write(SystemProto.BatteryDischarge.UPPER_BOUND_SINCE_CHARGE,
+ getHighDischargeAmountSinceCharge());
+ proto.write(SystemProto.BatteryDischarge.SCREEN_ON_SINCE_CHARGE,
+ getDischargeAmountScreenOnSinceCharge());
+ proto.write(SystemProto.BatteryDischarge.SCREEN_OFF_SINCE_CHARGE,
+ getDischargeAmountScreenOffSinceCharge());
+ proto.write(SystemProto.BatteryDischarge.SCREEN_DOZE_SINCE_CHARGE,
+ getDischargeAmountScreenDozeSinceCharge());
+ proto.write(SystemProto.BatteryDischarge.TOTAL_MAH,
+ getUahDischarge(which) / 1000);
+ proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_OFF,
+ getUahDischargeScreenOff(which) / 1000);
+ proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE,
+ getUahDischargeScreenDoze(which) / 1000);
+ proto.end(token);
+
+ // Time remaining
+ long timeRemainingUs = computeChargeTimeRemaining(rawRealtimeUs);
+ if (timeRemainingUs >= 0) {
+ // Charge time remaining (CHARGE_TIME_REMAIN_DATA)
+ proto.write(SystemProto.CHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
+ } else {
+ timeRemainingUs = computeBatteryTimeRemaining(rawRealtimeUs);
+ // Discharge time remaining (DISCHARGE_TIME_REMAIN_DATA)
+ if (timeRemainingUs >= 0) {
+ proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
+ } else {
+ proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, -1);
+ }
+ }
+
+ // Charge step (CHARGE_STEP_DATA)
+ dumpDurationSteps(proto, SystemProto.CHARGE_STEP, getChargeLevelStepTracker());
+
+ // Phone data connection (DATA_CONNECTION_TIME_DATA and DATA_CONNECTION_COUNT_DATA)
+ for (int i = 0; i < NUM_DATA_CONNECTION_TYPES; ++i) {
+ token = proto.start(SystemProto.DATA_CONNECTION);
+ proto.write(SystemProto.DataConnection.NAME, i);
+ dumpTimer(proto, SystemProto.DataConnection.TOTAL, getPhoneDataConnectionTimer(i),
+ rawRealtimeUs, which);
+ proto.end(token);
+ }
+
+ // Discharge step (DISCHARGE_STEP_DATA)
+ dumpDurationSteps(proto, SystemProto.DISCHARGE_STEP, getDischargeLevelStepTracker());
+
+ // CPU frequencies (GLOBAL_CPU_FREQ_DATA)
+ final long[] cpuFreqs = getCpuFreqs();
+ if (cpuFreqs != null) {
+ for (long i : cpuFreqs) {
+ proto.write(SystemProto.CPU_FREQUENCY, i);
+ }
+ }
+
+ // Bluetooth controller (GLOBAL_BLUETOOTH_CONTROLLER_DATA)
+ dumpControllerActivityProto(proto, SystemProto.GLOBAL_BLUETOOTH_CONTROLLER,
+ getBluetoothControllerActivity(), which);
+
+ // Modem controller (GLOBAL_MODEM_CONTROLLER_DATA)
+ dumpControllerActivityProto(proto, SystemProto.GLOBAL_MODEM_CONTROLLER,
+ getModemControllerActivity(), which);
+
+ // Global network data (GLOBAL_NETWORK_DATA)
+ token = proto.start(SystemProto.GLOBAL_NETWORK);
+ proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_RX,
+ getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_TX,
+ getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_RX,
+ getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_TX,
+ getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_RX,
+ getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_TX,
+ getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_RX,
+ getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_TX,
+ getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.BT_BYTES_RX,
+ getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
+ proto.write(SystemProto.GlobalNetwork.BT_BYTES_TX,
+ getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
+ proto.end(token);
+
+ // Wifi controller (GLOBAL_WIFI_CONTROLLER_DATA)
+ dumpControllerActivityProto(proto, SystemProto.GLOBAL_WIFI_CONTROLLER,
+ getWifiControllerActivity(), which);
+
+
+ // Global wifi (GLOBAL_WIFI_DATA)
+ token = proto.start(SystemProto.GLOBAL_WIFI);
+ proto.write(SystemProto.GlobalWifi.ON_DURATION_MS,
+ getWifiOnTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.GlobalWifi.RUNNING_DURATION_MS,
+ getGlobalWifiRunningTime(rawRealtimeUs, which) / 1000);
+ proto.end(token);
+
+ // Kernel wakelock (KERNEL_WAKELOCK_DATA)
+ final Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats();
+ for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) {
+ token = proto.start(SystemProto.KERNEL_WAKELOCK);
+ proto.write(SystemProto.KernelWakelock.NAME, ent.getKey());
+ dumpTimer(proto, SystemProto.KernelWakelock.TOTAL, ent.getValue(),
+ rawRealtimeUs, which);
+ proto.end(token);
+ }
+
+ // Misc (MISC_DATA)
+ // Calculate wakelock times across all uids.
+ long fullWakeLockTimeTotalUs = 0;
+ long partialWakeLockTimeTotalUs = 0;
+
+ final SparseArray<? extends Uid> uidStats = getUidStats();
+ for (int iu = 0; iu < uidStats.size(); iu++) {
+ final Uid u = uidStats.valueAt(iu);
+
+ final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks =
+ u.getWakelockStats();
+ for (int iw = wakelocks.size() - 1; iw >= 0; --iw) {
+ final Uid.Wakelock wl = wakelocks.valueAt(iw);
+
+ final Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL);
+ if (fullWakeTimer != null) {
+ fullWakeLockTimeTotalUs += fullWakeTimer.getTotalTimeLocked(rawRealtimeUs,
+ which);
+ }
+
+ final Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+ if (partialWakeTimer != null) {
+ partialWakeLockTimeTotalUs += partialWakeTimer.getTotalTimeLocked(
+ rawRealtimeUs, which);
+ }
+ }
+ }
+ token = proto.start(SystemProto.MISC);
+ proto.write(SystemProto.Misc.SCREEN_ON_DURATION_MS,
+ getScreenOnTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.PHONE_ON_DURATION_MS,
+ getPhoneOnTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.FULL_WAKELOCK_TOTAL_DURATION_MS,
+ fullWakeLockTimeTotalUs / 1000);
+ proto.write(SystemProto.Misc.PARTIAL_WAKELOCK_TOTAL_DURATION_MS,
+ partialWakeLockTimeTotalUs / 1000);
+ proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_DURATION_MS,
+ getMobileRadioActiveTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_ADJUSTED_TIME_MS,
+ getMobileRadioActiveAdjustedTime(which) / 1000);
+ proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_COUNT,
+ getMobileRadioActiveCount(which));
+ proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_UNKNOWN_DURATION_MS,
+ getMobileRadioActiveUnknownTime(which) / 1000);
+ proto.write(SystemProto.Misc.INTERACTIVE_DURATION_MS,
+ getInteractiveTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.BATTERY_SAVER_MODE_ENABLED_DURATION_MS,
+ getPowerSaveModeEnabledTime(rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.NUM_CONNECTIVITY_CHANGES,
+ getNumConnectivityChange(which));
+ proto.write(SystemProto.Misc.DEEP_DOZE_ENABLED_DURATION_MS,
+ getDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.DEEP_DOZE_COUNT,
+ getDeviceIdleModeCount(DEVICE_IDLE_MODE_DEEP, which));
+ proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_DURATION_MS,
+ getDeviceIdlingTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_COUNT,
+ getDeviceIdlingCount(DEVICE_IDLE_MODE_DEEP, which));
+ proto.write(SystemProto.Misc.LONGEST_DEEP_DOZE_DURATION_MS,
+ getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP));
+ proto.write(SystemProto.Misc.LIGHT_DOZE_ENABLED_DURATION_MS,
+ getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.LIGHT_DOZE_COUNT,
+ getDeviceIdleModeCount(DEVICE_IDLE_MODE_LIGHT, which));
+ proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_DURATION_MS,
+ getDeviceIdlingTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000);
+ proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_COUNT,
+ getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which));
+ proto.write(SystemProto.Misc.LONGEST_LIGHT_DOZE_DURATION_MS,
+ getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT));
+ proto.end(token);
+
+ final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly);
+ helper.create(this);
+ helper.refreshStats(which, UserHandle.USER_ALL);
+
+ // Power use item (POWER_USE_ITEM_DATA)
+ final List<BatterySipper> sippers = helper.getUsageList();
+ if (sippers != null) {
+ for (int i = 0; i < sippers.size(); ++i) {
+ final BatterySipper bs = sippers.get(i);
+ int n = SystemProto.PowerUseItem.UNKNOWN_SIPPER;
+ int uid = 0;
+ switch (bs.drainType) {
+ case IDLE:
+ n = SystemProto.PowerUseItem.IDLE;
+ break;
+ case CELL:
+ n = SystemProto.PowerUseItem.CELL;
+ break;
+ case PHONE:
+ n = SystemProto.PowerUseItem.PHONE;
+ break;
+ case WIFI:
+ n = SystemProto.PowerUseItem.WIFI;
+ break;
+ case BLUETOOTH:
+ n = SystemProto.PowerUseItem.BLUETOOTH;
+ break;
+ case SCREEN:
+ n = SystemProto.PowerUseItem.SCREEN;
+ break;
+ case FLASHLIGHT:
+ n = SystemProto.PowerUseItem.FLASHLIGHT;
+ break;
+ case APP:
+ // dumpProtoAppLocked will handle this.
+ continue;
+ case USER:
+ n = SystemProto.PowerUseItem.USER;
+ uid = UserHandle.getUid(bs.userId, 0);
+ break;
+ case UNACCOUNTED:
+ n = SystemProto.PowerUseItem.UNACCOUNTED;
+ break;
+ case OVERCOUNTED:
+ n = SystemProto.PowerUseItem.OVERCOUNTED;
+ break;
+ case CAMERA:
+ n = SystemProto.PowerUseItem.CAMERA;
+ break;
+ case MEMORY:
+ n = SystemProto.PowerUseItem.MEMORY;
+ break;
+ }
+ token = proto.start(SystemProto.POWER_USE_ITEM);
+ proto.write(SystemProto.PowerUseItem.NAME, n);
+ proto.write(SystemProto.PowerUseItem.UID, uid);
+ proto.write(SystemProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
+ proto.write(SystemProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
+ proto.write(SystemProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
+ proto.write(SystemProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
+ bs.proportionalSmearMah);
+ proto.end(token);
+ }
+ }
+
+ // Power use summary (POWER_USE_SUMMARY_DATA)
+ token = proto.start(SystemProto.POWER_USE_SUMMARY);
+ proto.write(SystemProto.PowerUseSummary.BATTERY_CAPACITY_MAH,
+ helper.getPowerProfile().getBatteryCapacity());
+ proto.write(SystemProto.PowerUseSummary.COMPUTED_POWER_MAH, helper.getComputedPower());
+ proto.write(SystemProto.PowerUseSummary.MIN_DRAINED_POWER_MAH, helper.getMinDrainedPower());
+ proto.write(SystemProto.PowerUseSummary.MAX_DRAINED_POWER_MAH, helper.getMaxDrainedPower());
+ proto.end(token);
+
+ // RPM stats (RESOURCE_POWER_MANAGER_DATA)
+ final Map<String, ? extends Timer> rpmStats = getRpmStats();
+ final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
+ for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
+ token = proto.start(SystemProto.RESOURCE_POWER_MANAGER);
+ proto.write(SystemProto.ResourcePowerManager.NAME, ent.getKey());
+ dumpTimer(proto, SystemProto.ResourcePowerManager.TOTAL,
+ ent.getValue(), rawRealtimeUs, which);
+ dumpTimer(proto, SystemProto.ResourcePowerManager.SCREEN_OFF,
+ screenOffRpmStats.get(ent.getKey()), rawRealtimeUs, which);
+ proto.end(token);
+ }
+
+ // Screen brightness (SCREEN_BRIGHTNESS_DATA)
+ for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; ++i) {
+ token = proto.start(SystemProto.SCREEN_BRIGHTNESS);
+ proto.write(SystemProto.ScreenBrightness.NAME, i);
+ dumpTimer(proto, SystemProto.ScreenBrightness.TOTAL, getScreenBrightnessTimer(i),
+ rawRealtimeUs, which);
+ proto.end(token);
+ }
+
+ // Signal scanning time (SIGNAL_SCANNING_TIME_DATA)
+ dumpTimer(proto, SystemProto.SIGNAL_SCANNING, getPhoneSignalScanningTimer(), rawRealtimeUs,
+ which);
+
+ // Phone signal strength (SIGNAL_STRENGTH_TIME_DATA and SIGNAL_STRENGTH_COUNT_DATA)
+ for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; ++i) {
+ token = proto.start(SystemProto.PHONE_SIGNAL_STRENGTH);
+ proto.write(SystemProto.PhoneSignalStrength.NAME, i);
+ dumpTimer(proto, SystemProto.PhoneSignalStrength.TOTAL, getPhoneSignalStrengthTimer(i),
+ rawRealtimeUs, which);
+ proto.end(token);
+ }
+
+ // Wakeup reasons (WAKEUP_REASON_DATA)
+ final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats();
+ for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) {
+ token = proto.start(SystemProto.WAKEUP_REASON);
+ proto.write(SystemProto.WakeupReason.NAME, ent.getKey());
+ dumpTimer(proto, SystemProto.WakeupReason.TOTAL, ent.getValue(), rawRealtimeUs, which);
+ proto.end(token);
+ }
+
+ // Wifi signal strength (WIFI_SIGNAL_STRENGTH_TIME_DATA and WIFI_SIGNAL_STRENGTH_COUNT_DATA)
+ for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; ++i) {
+ token = proto.start(SystemProto.WIFI_SIGNAL_STRENGTH);
+ proto.write(SystemProto.WifiSignalStrength.NAME, i);
+ dumpTimer(proto, SystemProto.WifiSignalStrength.TOTAL, getWifiSignalStrengthTimer(i),
+ rawRealtimeUs, which);
+ proto.end(token);
+ }
+
+ // Wifi state (WIFI_STATE_TIME_DATA and WIFI_STATE_COUNT_DATA)
+ for (int i = 0; i < NUM_WIFI_STATES; ++i) {
+ token = proto.start(SystemProto.WIFI_STATE);
+ proto.write(SystemProto.WifiState.NAME, i);
+ dumpTimer(proto, SystemProto.WifiState.TOTAL, getWifiStateTimer(i),
+ rawRealtimeUs, which);
+ proto.end(token);
+ }
+
+ // Wifi supplicant state (WIFI_SUPPL_STATE_TIME_DATA and WIFI_SUPPL_STATE_COUNT_DATA)
+ for (int i = 0; i < NUM_WIFI_SUPPL_STATES; ++i) {
+ token = proto.start(SystemProto.WIFI_SUPPLICANT_STATE);
+ proto.write(SystemProto.WifiSupplicantState.NAME, i);
+ dumpTimer(proto, SystemProto.WifiSupplicantState.TOTAL, getWifiSupplStateTimer(i),
+ rawRealtimeUs, which);
+ proto.end(token);
+ }
+
+ proto.end(sToken);
+ }
}
diff --git a/android/os/Debug.java b/android/os/Debug.java
index b46c6b16..017c2134 100644
--- a/android/os/Debug.java
+++ b/android/os/Debug.java
@@ -1748,22 +1748,26 @@ public final class Debug
public static final int MEMINFO_SHMEM = 4;
/** @hide */
public static final int MEMINFO_SLAB = 5;
+ /** @hide */
+ public static final int MEMINFO_SLAB_RECLAIMABLE = 6;
+ /** @hide */
+ public static final int MEMINFO_SLAB_UNRECLAIMABLE = 7;
/** @hide */
- public static final int MEMINFO_SWAP_TOTAL = 6;
+ public static final int MEMINFO_SWAP_TOTAL = 8;
/** @hide */
- public static final int MEMINFO_SWAP_FREE = 7;
+ public static final int MEMINFO_SWAP_FREE = 9;
/** @hide */
- public static final int MEMINFO_ZRAM_TOTAL = 8;
+ public static final int MEMINFO_ZRAM_TOTAL = 10;
/** @hide */
- public static final int MEMINFO_MAPPED = 9;
+ public static final int MEMINFO_MAPPED = 11;
/** @hide */
- public static final int MEMINFO_VM_ALLOC_USED = 10;
+ public static final int MEMINFO_VM_ALLOC_USED = 12;
/** @hide */
- public static final int MEMINFO_PAGE_TABLES = 11;
+ public static final int MEMINFO_PAGE_TABLES = 13;
/** @hide */
- public static final int MEMINFO_KERNEL_STACK = 12;
+ public static final int MEMINFO_KERNEL_STACK = 14;
/** @hide */
- public static final int MEMINFO_COUNT = 13;
+ public static final int MEMINFO_COUNT = 15;
/**
* Retrieves /proc/meminfo. outSizes is filled with fields
diff --git a/android/os/ParcelFileDescriptor.java b/android/os/ParcelFileDescriptor.java
index c091420a..7f588adb 100644
--- a/android/os/ParcelFileDescriptor.java
+++ b/android/os/ParcelFileDescriptor.java
@@ -737,7 +737,9 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
private void closeWithStatus(int status, String msg) {
if (mClosed) return;
mClosed = true;
- mGuard.close();
+ if (mGuard != null) {
+ mGuard.close();
+ }
// Status MUST be sent before closing actual descriptor
writeCommStatusAndClose(status, msg);
IoUtils.closeQuietly(mFd);
diff --git a/android/os/PatternMatcher.java b/android/os/PatternMatcher.java
index 1f3a1e68..76b21426 100644
--- a/android/os/PatternMatcher.java
+++ b/android/os/PatternMatcher.java
@@ -16,7 +16,7 @@
package android.os;
-import android.util.Log;
+import android.util.proto.ProtoOutputStream;
import java.util.Arrays;
@@ -131,7 +131,17 @@ public class PatternMatcher implements Parcelable {
}
return "PatternMatcher{" + type + mPattern + "}";
}
-
+
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(PatternMatcherProto.PATTERN, mPattern);
+ proto.write(PatternMatcherProto.TYPE, mType);
+ // PatternMatcherProto.PARSED_PATTERN is too much to dump, but the field is reserved to
+ // match the current data structure.
+ proto.end(token);
+ }
+
public int describeContents() {
return 0;
}
@@ -141,7 +151,7 @@ public class PatternMatcher implements Parcelable {
dest.writeInt(mType);
dest.writeIntArray(mParsedPattern);
}
-
+
public PatternMatcher(Parcel src) {
mPattern = src.readString();
mType = src.readInt();
diff --git a/android/os/ServiceManager.java b/android/os/ServiceManager.java
index f41848fa..34c78455 100644
--- a/android/os/ServiceManager.java
+++ b/android/os/ServiceManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2009 The Android Open 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,29 +16,9 @@
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.
@@ -47,32 +27,14 @@ 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;
}
/**
- * Returns a reference to a service with the given name, or throws
- * {@link NullPointerException} if none is found.
- *
- * @hide
+ * Is not supposed to return null, but that is fine for layoutlib.
*/
public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
- final IBinder binder = getService(name);
- if (binder != null) {
- return binder;
- } else {
- throw new ServiceNotFoundException(name);
- }
+ throw new ServiceNotFoundException(name);
}
/**
@@ -83,39 +45,7 @@ public final class ServiceManager {
* @param service the service object
*/
public static void addService(String name, IBinder service) {
- 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);
- }
+ // pass
}
/**
@@ -123,17 +53,7 @@ public final class ServiceManager {
* service manager. Non-blocking.
*/
public static IBinder checkService(String name) {
- 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;
- }
+ return null;
}
/**
@@ -142,12 +62,9 @@ public final class ServiceManager {
* case of an exception
*/
public static String[] listServices() {
- try {
- return getIServiceManager().listServices(IServiceManager.DUMP_PRIORITY_ALL);
- } catch (RemoteException e) {
- Log.e(TAG, "error in listServices", e);
- return null;
- }
+ // actual implementation returns null sometimes, so it's ok
+ // to return null instead of an empty list.
+ return null;
}
/**
@@ -159,10 +76,7 @@ public final class ServiceManager {
* @hide
*/
public static void initServiceCache(Map<String, IBinder> cache) {
- if (sCache.size() != 0) {
- throw new IllegalStateException("setServiceCache may only be called once");
- }
- sCache.putAll(cache);
+ // pass
}
/**
@@ -173,6 +87,7 @@ 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/SystemProperties.java b/android/os/SystemProperties.java
index 560b4b31..84111fbf 100644
--- a/android/os/SystemProperties.java
+++ b/android/os/SystemProperties.java
@@ -84,9 +84,6 @@ public class SystemProperties {
/**
* Get the String value for the given {@code key}.
*
- * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This
- * method will crash in native code.
- *
* @param key the key to lookup
* @return an empty string if the {@code key} isn't found
*/
@@ -99,9 +96,6 @@ public class SystemProperties {
/**
* Get the String value for the given {@code key}.
*
- * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This
- * method will crash in native code.
- *
* @param key the key to lookup
* @param def the default value in case the property is not set or empty
* @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty
diff --git a/android/os/UserManager.java b/android/os/UserManager.java
index 430a5e3e..8c688713 100644
--- a/android/os/UserManager.java
+++ b/android/os/UserManager.java
@@ -574,6 +574,25 @@ public class UserManager {
public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows";
/**
+ * Specifies that system error dialogs for crashed or unresponsive apps should not be shown.
+ * In this case, the system will force-stop the app as if the user chooses the "close app"
+ * option on the UI. No feedback report will be collected as there is no way for the user to
+ * provide explicit consent.
+ *
+ * When this user restriction is set by device owners, it's applied to all users; when it's set
+ * by profile owners, it's only applied to the relevant profiles.
+ * The default value is <code>false</code>.
+ *
+ * <p>This user restriction has no effect on managed profiles.
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
+
+ /**
* Specifies if what is copied in the clipboard of this profile can
* be pasted in related profiles. Does not restrict if the clipboard of related profiles can be
* pasted in this profile.
diff --git a/android/preference/SeekBarVolumizer.java b/android/preference/SeekBarVolumizer.java
index ee8eed19..3d2e1d1f 100644
--- a/android/preference/SeekBarVolumizer.java
+++ b/android/preference/SeekBarVolumizer.java
@@ -206,8 +206,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
try {
mRingtone.setAudioAttributes(new AudioAttributes.Builder(mRingtone
.getAudioAttributes())
- .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
- AudioAttributes.FLAG_BYPASS_MUTE)
+ .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
.build());
mRingtone.play();
} catch (Throwable e) {
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index a062db43..b4350746 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -5708,6 +5708,7 @@ public final class Settings {
*
* @hide
*/
+ @TestApi
public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
"accessibility_display_magnification_enabled";
@@ -6792,14 +6793,6 @@ public final class Settings {
"lock_screen_show_notifications";
/**
- * This preference stores the last stack active task time for each user, which affects what
- * tasks will be visible in Overview.
- * @hide
- */
- public static final String OVERVIEW_LAST_STACK_ACTIVE_TIME =
- "overview_last_stack_active_time";
-
- /**
* List of TV inputs that are currently hidden. This is a string
* containing the IDs of all hidden TV inputs. Each ID is encoded by
* {@link android.net.Uri#encode(String)} and separated by ':'.
@@ -9575,6 +9568,22 @@ public final class Settings {
public static final String DEVICE_POLICY_CONSTANTS = "device_policy_constants";
/**
+ * TextClassifier specific settings.
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * <pre>
+ * smart_selection_dark_launch (boolean)
+ * smart_selection_enabled_for_edit_text (boolean)
+ * </pre>
+ *
+ * <p>
+ * Type: string
+ * @hide
+ * see also android.view.textclassifier.TextClassifierConstants
+ */
+ public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants";
+
+ /**
* Get the key that retrieves a bluetooth headset's priority.
* @hide
*/
diff --git a/android/service/autofill/AutofillService.java b/android/service/autofill/AutofillService.java
index 2e59f6c5..953501c7 100644
--- a/android/service/autofill/AutofillService.java
+++ b/android/service/autofill/AutofillService.java
@@ -65,7 +65,7 @@ import com.android.internal.os.SomeArgs;
* <li>The service replies through {@link FillCallback#onSuccess(FillResponse)}.
* <li>The Android System calls {@link #onDisconnected()} and unbinds from the
* {@code AutofillService}.
- * <li>The Android System displays an UI affordance with the options sent by the service.
+ * <li>The Android System displays an autofill UI with the options sent by the service.
* <li>The user picks an option.
* <li>The proper views are autofilled.
* </ol>
@@ -365,6 +365,81 @@ import com.android.internal.os.SomeArgs;
* <p><b>Note:</b> The autofill service could also whitelist well-known browser apps and skip the
* verifications above, as long as the service can verify the authenticity of the browser app by
* checking its signing certificate.
+ *
+ * <a name="MultipleStepsSave"></a>
+ * <h3>Saving when data is split in multiple screens</h3>
+ *
+ * Apps often split the user data in multiple screens in the same activity, specially in
+ * activities used to create a new user account. For example, the first screen asks for a username,
+ * and if the username is available, it moves to a second screen, which asks for a password.
+ *
+ * <p>It's tricky to handle save for autofill in these situations, because the autofill service must
+ * wait until the user enters both fields before the autofill save UI can be shown. But it can be
+ * done by following the steps below:
+ *
+ * <ol>
+ * <li>In the first
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
+ * adds a {@link FillResponse.Builder#setClientState(android.os.Bundle) client state bundle} in
+ * the response, containing the autofill ids of the partial fields present in the screen.
+ * <li>In the second
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
+ * retrieves the {@link FillRequest#getClientState() client state bundle}, gets the autofill ids
+ * set in the previous request from the client state, and adds these ids and the
+ * {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} to the {@link SaveInfo} used in the second
+ * response.
+ * <li>In the {@link #onSaveRequest(SaveRequest, SaveCallback) save request}, the service uses the
+ * proper {@link FillContext fill contexts} to get the value of each field (there is one fill
+ * context per fill request).
+ * </ol>
+ *
+ * <p>For example, in an app that uses 2 steps for the username and password fields, the workflow
+ * would be:
+ * <pre class="prettyprint">
+ * // On first fill request
+ * AutofillId usernameId = // parse from AssistStructure;
+ * Bundle clientState = new Bundle();
+ * clientState.putParcelable("usernameId", usernameId);
+ * fillCallback.onSuccess(
+ * new FillResponse.Builder()
+ * .setClientState(clientState)
+ * .setSaveInfo(new SaveInfo
+ * .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME, new AutofillId[] {usernameId})
+ * .build())
+ * .build());
+ *
+ * // On second fill request
+ * Bundle clientState = fillRequest.getClientState();
+ * AutofillId usernameId = clientState.getParcelable("usernameId");
+ * AutofillId passwordId = // parse from AssistStructure
+ * clientState.putParcelable("passwordId", passwordId);
+ * fillCallback.onSuccess(
+ * new FillResponse.Builder()
+ * .setClientState(clientState)
+ * .setSaveInfo(new SaveInfo
+ * .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ * new AutofillId[] {usernameId, passwordId})
+ * .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+ * .build())
+ * .build());
+ *
+ * // On save request
+ * Bundle clientState = saveRequest.getClientState();
+ * AutofillId usernameId = clientState.getParcelable("usernameId");
+ * AutofillId passwordId = clientState.getParcelable("passwordId");
+ * List<FillContext> fillContexts = saveRequest.getFillContexts();
+ *
+ * FillContext usernameContext = fillContexts.get(0);
+ * ViewNode usernameNode = findNodeByAutofillId(usernameContext.getStructure(), usernameId);
+ * AutofillValue username = usernameNode.getAutofillValue().getTextValue().toString();
+ *
+ * FillContext passwordContext = fillContexts.get(1);
+ * ViewNode passwordNode = findNodeByAutofillId(passwordContext.getStructure(), passwordId);
+ * AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
+ *
+ * save(username, password);
+ *
+ * </pre>
*/
public abstract class AutofillService extends Service {
private static final String TAG = "AutofillService";
diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java
index 1b9240cc..fde2416f 100644
--- a/android/service/autofill/SaveInfo.java
+++ b/android/service/autofill/SaveInfo.java
@@ -68,7 +68,7 @@ import java.util.Arrays;
* .build();
* </pre>
*
- * <p>The save type flags are used to display the appropriate strings in the save UI affordance.
+ * <p>The save type flags are used to display the appropriate strings in the autofill save UI.
* You can pass multiple values, but try to keep it short if possible. In the above example, just
* {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough.
*
@@ -103,13 +103,17 @@ import java.util.Arrays;
* .build();
* </pre>
*
+ * <a name="TriggeringSaveRequest"></a>
+ * <h3>Triggering a save request</h3>
+ *
* <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after
* any of the following events:
* <ul>
* <li>The {@link Activity} finishes.
- * <li>The app explicitly called {@link AutofillManager#commit()}.
- * <li>All required views became invisible (if the {@link SaveInfo} was created with the
+ * <li>The app explicitly calls {@link AutofillManager#commit()}.
+ * <li>All required views become invisible (if the {@link SaveInfo} was created with the
* {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag).
+ * <li>The user clicks a specific view (defined by {@link Builder#setTriggerId(AutofillId)}.
* </ul>
*
* <p>But it is only triggered when all conditions below are met:
@@ -123,10 +127,13 @@ import java.util.Arrays;
* <li>There is no {@link Dataset} in the last {@link FillResponse} that completely matches the
* screen state (i.e., all required and optional fields in the dataset have the same value as
* the fields in the screen).
- * <li>The user explicitly tapped the UI affordance asking to save data for autofill.
+ * <li>The user explicitly tapped the autofill save UI asking to save data for autofill.
* </ul>
*
- * <p>The service can also customize some aspects of the save UI affordance:
+ * <a name="CustomizingSaveUI"></a>
+ * <h3>Customizing the autofill save UI</h3>
+ *
+ * <p>The service can also customize some aspects of the autofill save UI:
* <ul>
* <li>Add a simple subtitle by calling {@link Builder#setDescription(CharSequence)}.
* <li>Add a customized subtitle by calling
@@ -212,16 +219,25 @@ public final class SaveInfo implements Parcelable {
@interface SaveDataType{}
/**
- * Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
- * is called once the {@link Activity} finishes. If this flag is set it is called once all
- * saved views become invisible.
+ * Usually, a save request is only automatically <a href="#TriggeringSaveRequest">triggered</a>
+ * once the {@link Activity} finishes. If this flag is set, it is triggered once all saved views
+ * become invisible.
*/
public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1;
+ /**
+ * By default, a save request is automatically <a href="#TriggeringSaveRequest">triggered</a>
+ * once the {@link Activity} finishes. If this flag is set, finishing the activity doesn't
+ * trigger a save request.
+ *
+ * <p>This flag is typically used in conjunction with {@link Builder#setTriggerId(AutofillId)}.
+ */
+ public static final int FLAG_DONT_SAVE_ON_FINISH = 0x2;
+
/** @hide */
@IntDef(
flag = true,
- value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE})
+ value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE, FLAG_DONT_SAVE_ON_FINISH})
@Retention(RetentionPolicy.SOURCE)
@interface SaveInfoFlags{}
@@ -236,6 +252,7 @@ public final class SaveInfo implements Parcelable {
private final InternalValidator mValidator;
private final InternalSanitizer[] mSanitizerKeys;
private final AutofillId[][] mSanitizerValues;
+ private final AutofillId mTriggerId;
private SaveInfo(Builder builder) {
mType = builder.mType;
@@ -259,6 +276,7 @@ public final class SaveInfo implements Parcelable {
mSanitizerValues[i] = builder.mSanitizers.valueAt(i);
}
}
+ mTriggerId = builder.mTriggerId;
}
/** @hide */
@@ -320,6 +338,12 @@ public final class SaveInfo implements Parcelable {
return mSanitizerValues;
}
+ /** @hide */
+ @Nullable
+ public AutofillId getTriggerId() {
+ return mTriggerId;
+ }
+
/**
* A builder for {@link SaveInfo} objects.
*/
@@ -338,6 +362,7 @@ public final class SaveInfo implements Parcelable {
private ArrayMap<InternalSanitizer, AutofillId[]> mSanitizers;
// Set used to validate against duplicate ids.
private ArraySet<AutofillId> mSanitizerIds;
+ private AutofillId mTriggerId;
/**
* Creates a new builder.
@@ -394,13 +419,15 @@ public final class SaveInfo implements Parcelable {
/**
* Sets flags changing the save behavior.
*
- * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}.
+ * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE},
+ * {@link #FLAG_DONT_SAVE_ON_FINISH}, or {@code 0}.
* @return This builder.
*/
public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
throwIfDestroyed();
- mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
+ mFlags = Preconditions.checkFlagsArgument(flags,
+ FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE | FLAG_DONT_SAVE_ON_FINISH);
return this;
}
@@ -493,8 +520,8 @@ public final class SaveInfo implements Parcelable {
}
/**
- * Sets an object used to validate the user input - if the input is not valid, the Save UI
- * affordance is not shown.
+ * Sets an object used to validate the user input - if the input is not valid, the
+ * autofill save UI is not shown.
*
* <p>Typically used to validate credit card numbers. Examples:
*
@@ -520,7 +547,7 @@ public final class SaveInfo implements Parcelable {
* );
* </pre>
*
- * <p><b>NOTE: </b>the example above is just for illustrative purposes; the same validator
+ * <p><b>Note:</b> the example above is just for illustrative purposes; the same validator
* could be created using a single regex for the {@code OR} part:
*
* <pre class="prettyprint">
@@ -615,6 +642,27 @@ public final class SaveInfo implements Parcelable {
return this;
}
+ /**
+ * Explicitly defines the view that should commit the autofill context when clicked.
+ *
+ * <p>Usually, the save request is only automatically
+ * <a href="#TriggeringSaveRequest">triggered</a> after the activity is
+ * finished or all relevant views become invisible, but there are scenarios where the
+ * autofill context is automatically commited too late
+ * &mdash;for example, when the activity manually clears the autofillable views when a
+ * button is tapped. This method can be used to trigger the autofill save UI earlier in
+ * these scenarios.
+ *
+ * <p><b>Note:</b> This method should only be used in scenarios where the automatic workflow
+ * is not enough, otherwise it could trigger the autofill save UI when it should not&mdash;
+ * for example, when the user entered invalid credentials for the autofillable views.
+ */
+ public @NonNull Builder setTriggerId(@NonNull AutofillId id) {
+ throwIfDestroyed();
+ mTriggerId = Preconditions.checkNotNull(id);
+ return this;
+ }
+
/**
* Builds a new {@link SaveInfo} instance.
*
@@ -652,13 +700,14 @@ public final class SaveInfo implements Parcelable {
.append(", description=").append(mDescription)
.append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_",
mNegativeButtonStyle))
- .append(", mFlags=").append(mFlags)
- .append(", mCustomDescription=").append(mCustomDescription)
- .append(", validation=").append(mValidator)
+ .append(", flags=").append(mFlags)
+ .append(", customDescription=").append(mCustomDescription)
+ .append(", validator=").append(mValidator)
.append(", sanitizerKeys=")
.append(mSanitizerKeys == null ? "N/A:" : mSanitizerKeys.length)
.append(", sanitizerValues=")
.append(mSanitizerValues == null ? "N/A:" : mSanitizerValues.length)
+ .append(", triggerId=").append(mTriggerId)
.append("]").toString();
}
@@ -687,6 +736,7 @@ public final class SaveInfo implements Parcelable {
parcel.writeParcelableArray(mSanitizerValues[i], flags);
}
}
+ parcel.writeParcelable(mTriggerId, flags);
parcel.writeInt(mFlags);
}
@@ -727,6 +777,10 @@ public final class SaveInfo implements Parcelable {
builder.addSanitizer(sanitizers[i], autofillIds);
}
}
+ final AutofillId triggerId = parcel.readParcelable(null);
+ if (triggerId != null) {
+ builder.setTriggerId(triggerId);
+ }
builder.setFlags(parcel.readInt());
return builder.build();
}
diff --git a/android/service/autofill/SaveRequest.java b/android/service/autofill/SaveRequest.java
index 65fdb5c4..f53967bd 100644
--- a/android/service/autofill/SaveRequest.java
+++ b/android/service/autofill/SaveRequest.java
@@ -19,7 +19,6 @@ package android.service.autofill;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
-import android.os.CancellationSignal;
import android.os.Parcel;
import android.os.Parcelable;
@@ -60,9 +59,14 @@ public final class SaveRequest implements Parcelable {
}
/**
- * Gets the extra client state returned from the last {@link
- * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)}
- * fill request}.
+ * Gets the latest client state extra returned from the service.
+ *
+ * <p><b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, only client state
+ * bundles set by {@link FillResponse.Builder#setClientState(Bundle)} where considered. On
+ * Android {@link android.os.Build.VERSION_CODES#P} and higher, bundles set in the result of
+ * an authenticated request through the
+ * {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE} extra are
+ * also considered (and take precedence when set).
*
* @return The client state.
*/
diff --git a/android/service/notification/ZenModeConfig.java b/android/service/notification/ZenModeConfig.java
index 7bec898a..c5615ae6 100644
--- a/android/service/notification/ZenModeConfig.java
+++ b/android/service/notification/ZenModeConfig.java
@@ -76,10 +76,13 @@ public class ZenModeConfig implements Parcelable {
private static final int DAY_MINUTES = 24 * 60;
private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
- private static final boolean DEFAULT_ALLOW_CALLS = true;
+ // Default allow categories set in readXml() from default_zen_mode_config.xml, fallback values:
+ private static final boolean DEFAULT_ALLOW_ALARMS = true;
+ private static final boolean DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER = true;
+ private static final boolean DEFAULT_ALLOW_CALLS = false;
private static final boolean DEFAULT_ALLOW_MESSAGES = false;
- private static final boolean DEFAULT_ALLOW_REMINDERS = true;
- private static final boolean DEFAULT_ALLOW_EVENTS = true;
+ private static final boolean DEFAULT_ALLOW_REMINDERS = false;
+ private static final boolean DEFAULT_ALLOW_EVENTS = false;
private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false;
private static final boolean DEFAULT_ALLOW_SCREEN_OFF = true;
private static final boolean DEFAULT_ALLOW_SCREEN_ON = true;
@@ -89,6 +92,8 @@ public class ZenModeConfig implements Parcelable {
private static final String ZEN_ATT_VERSION = "version";
private static final String ZEN_ATT_USER = "user";
private static final String ALLOW_TAG = "allow";
+ private static final String ALLOW_ATT_ALARMS = "alarms";
+ private static final String ALLOW_ATT_MEDIA = "media_system_other";
private static final String ALLOW_ATT_CALLS = "calls";
private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers";
private static final String ALLOW_ATT_MESSAGES = "messages";
@@ -100,8 +105,6 @@ public class ZenModeConfig implements Parcelable {
private static final String ALLOW_ATT_SCREEN_OFF = "visualScreenOff";
private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
- private static final String CONDITION_TAG = "condition";
- private static final String CONDITION_ATT_COMPONENT = "component";
private static final String CONDITION_ATT_ID = "id";
private static final String CONDITION_ATT_SUMMARY = "summary";
private static final String CONDITION_ATT_LINE1 = "line1";
@@ -123,6 +126,8 @@ public class ZenModeConfig implements Parcelable {
private static final String RULE_ATT_CREATION_TIME = "creationTime";
private static final String RULE_ATT_ENABLER = "enabler";
+ public boolean allowAlarms = DEFAULT_ALLOW_ALARMS;
+ public boolean allowMediaSystemOther = DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER;
public boolean allowCalls = DEFAULT_ALLOW_CALLS;
public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS;
public boolean allowMessages = DEFAULT_ALLOW_MESSAGES;
@@ -161,6 +166,8 @@ public class ZenModeConfig implements Parcelable {
}
allowWhenScreenOff = source.readInt() == 1;
allowWhenScreenOn = source.readInt() == 1;
+ allowAlarms = source.readInt() == 1;
+ allowMediaSystemOther = source.readInt() == 1;
}
@Override
@@ -190,19 +197,23 @@ public class ZenModeConfig implements Parcelable {
}
dest.writeInt(allowWhenScreenOff ? 1 : 0);
dest.writeInt(allowWhenScreenOn ? 1 : 0);
+ dest.writeInt(allowAlarms ? 1 : 0);
+ dest.writeInt(allowMediaSystemOther ? 1 : 0);
}
@Override
public String toString() {
return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
.append("user=").append(user)
+ .append(",allowAlarms=").append(allowAlarms)
+ .append(",allowMediaSystemOther=").append(allowMediaSystemOther)
+ .append(",allowReminders=").append(allowReminders)
+ .append(",allowEvents=").append(allowEvents)
.append(",allowCalls=").append(allowCalls)
.append(",allowRepeatCallers=").append(allowRepeatCallers)
.append(",allowMessages=").append(allowMessages)
.append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
.append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
- .append(",allowReminders=").append(allowReminders)
- .append(",allowEvents=").append(allowEvents)
.append(",allowWhenScreenOff=").append(allowWhenScreenOff)
.append(",allowWhenScreenOn=").append(allowWhenScreenOn)
.append(",automaticRules=").append(automaticRules)
@@ -218,9 +229,21 @@ public class ZenModeConfig implements Parcelable {
if (user != to.user) {
d.addLine("user", user, to.user);
}
+ if (allowAlarms != to.allowAlarms) {
+ d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
+ }
+ if (allowMediaSystemOther != to.allowMediaSystemOther) {
+ d.addLine("allowMediaSystemOther", allowMediaSystemOther, to.allowMediaSystemOther);
+ }
if (allowCalls != to.allowCalls) {
d.addLine("allowCalls", allowCalls, to.allowCalls);
}
+ if (allowReminders != to.allowReminders) {
+ d.addLine("allowReminders", allowReminders, to.allowReminders);
+ }
+ if (allowEvents != to.allowEvents) {
+ d.addLine("allowEvents", allowEvents, to.allowEvents);
+ }
if (allowRepeatCallers != to.allowRepeatCallers) {
d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
}
@@ -233,12 +256,6 @@ public class ZenModeConfig implements Parcelable {
if (allowMessagesFrom != to.allowMessagesFrom) {
d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
}
- if (allowReminders != to.allowReminders) {
- d.addLine("allowReminders", allowReminders, to.allowReminders);
- }
- if (allowEvents != to.allowEvents) {
- d.addLine("allowEvents", allowEvents, to.allowEvents);
- }
if (allowWhenScreenOff != to.allowWhenScreenOff) {
d.addLine("allowWhenScreenOff", allowWhenScreenOff, to.allowWhenScreenOff);
}
@@ -335,7 +352,9 @@ public class ZenModeConfig implements Parcelable {
if (!(o instanceof ZenModeConfig)) return false;
if (o == this) return true;
final ZenModeConfig other = (ZenModeConfig) o;
- return other.allowCalls == allowCalls
+ return other.allowAlarms == allowAlarms
+ && other.allowMediaSystemOther == allowMediaSystemOther
+ && other.allowCalls == allowCalls
&& other.allowRepeatCallers == allowRepeatCallers
&& other.allowMessages == allowMessages
&& other.allowCallsFrom == allowCallsFrom
@@ -351,10 +370,10 @@ public class ZenModeConfig implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom,
- allowMessagesFrom, allowReminders, allowEvents, allowWhenScreenOff,
- allowWhenScreenOn,
- user, automaticRules, manualRule);
+ return Objects.hash(allowAlarms, allowMediaSystemOther, allowCalls,
+ allowRepeatCallers, allowMessages,
+ allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
+ allowWhenScreenOff, allowWhenScreenOn, user, automaticRules, manualRule);
}
private static String toDayList(int[] days) {
@@ -413,10 +432,12 @@ public class ZenModeConfig implements Parcelable {
}
if (type == XmlPullParser.START_TAG) {
if (ALLOW_TAG.equals(tag)) {
- rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+ rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS,
+ DEFAULT_ALLOW_CALLS);
rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS,
DEFAULT_ALLOW_REPEAT_CALLERS);
- rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+ rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES,
+ DEFAULT_ALLOW_MESSAGES);
rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
DEFAULT_ALLOW_REMINDERS);
rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS);
@@ -438,6 +459,9 @@ public class ZenModeConfig implements Parcelable {
safeBoolean(parser, ALLOW_ATT_SCREEN_OFF, DEFAULT_ALLOW_SCREEN_OFF);
rt.allowWhenScreenOn =
safeBoolean(parser, ALLOW_ATT_SCREEN_ON, DEFAULT_ALLOW_SCREEN_ON);
+ rt.allowAlarms = safeBoolean(parser, ALLOW_ATT_ALARMS, DEFAULT_ALLOW_ALARMS);
+ rt.allowMediaSystemOther = safeBoolean(parser, ALLOW_ATT_MEDIA,
+ DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER);
} else if (MANUAL_TAG.equals(tag)) {
rt.manualRule = readRuleXml(parser);
} else if (AUTOMATIC_TAG.equals(tag)) {
@@ -468,6 +492,8 @@ public class ZenModeConfig implements Parcelable {
out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom));
out.attribute(null, ALLOW_ATT_SCREEN_OFF, Boolean.toString(allowWhenScreenOff));
out.attribute(null, ALLOW_ATT_SCREEN_ON, Boolean.toString(allowWhenScreenOn));
+ out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowAlarms));
+ out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowMediaSystemOther));
out.endTag(null, ALLOW_TAG);
if (manualRule != null) {
@@ -654,6 +680,12 @@ public class ZenModeConfig implements Parcelable {
if (!allowWhenScreenOn) {
suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_ON;
}
+ if (allowAlarms) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS;
+ }
+ if (allowMediaSystemOther) {
+ priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER;
+ }
priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
@@ -680,10 +712,13 @@ public class ZenModeConfig implements Parcelable {
public void applyNotificationPolicy(Policy policy) {
if (policy == null) return;
- allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
- allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
+ allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0;
+ allowMediaSystemOther = (policy.priorityCategories
+ & Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0;
allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0;
allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
+ allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
+ allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)
!= 0;
allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom);
diff --git a/android/support/LibraryVersions.java b/android/support/LibraryVersions.java
index a046d95e..72f7fb18 100644
--- a/android/support/LibraryVersions.java
+++ b/android/support/LibraryVersions.java
@@ -45,15 +45,17 @@ public class LibraryVersions {
*/
public static final Version PAGING = new Version("1.0.0-alpha3");
+ private static final Version LIFECYCLES = new Version("1.0.2");
+
/**
* Version code for Lifecycle libs that are required by the support library
*/
- public static final Version LIFECYCLES_CORE = new Version("1.0.2");
+ public static final Version LIFECYCLES_CORE = LIFECYCLES;
/**
* Version code for Lifecycle runtime libs that are required by the support library
*/
- public static final Version LIFECYCLES_RUNTIME = new Version("1.0.0");
+ public static final Version LIFECYCLES_RUNTIME = LIFECYCLES;
/**
* Version code for shared code of flatfoot
diff --git a/android/support/car/drawer/CarDrawerActivity.java b/android/support/car/drawer/CarDrawerActivity.java
new file mode 100644
index 00000000..7100218a
--- /dev/null
+++ b/android/support/car/drawer/CarDrawerActivity.java
@@ -0,0 +1,152 @@
+/*
+ * 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.drawer;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.car.R;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Common base Activity for car apps that need to present a Drawer.
+ *
+ * <p>This Activity manages the overall layout. To use it, sub-classes need to:
+ *
+ * <ul>
+ * <li>Provide the root-items for the Drawer by implementing {@link #getRootAdapter()}.
+ * <li>Add their main content using {@link #setMainContent(int)} or {@link #setMainContent(View)}.
+ * They can also add fragments to the main-content container by obtaining its id using
+ * {@link #getContentContainerId()}
+ * </ul>
+ *
+ * <p>This class will take care of drawer toggling and display.
+ *
+ * <p>The rootAdapter can implement nested-navigation, in its click-handling, by passing the
+ * CarDrawerAdapter for the next level to
+ * {@link CarDrawerController#switchToAdapter(CarDrawerAdapter)}.
+ *
+ * <p>Any Activity's based on this class need to set their theme to CarDrawerActivityTheme or a
+ * derivative.
+ */
+public abstract class CarDrawerActivity extends AppCompatActivity {
+ private CarDrawerController mDrawerController;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.car_drawer_activity);
+
+ DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
+ ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(
+ this /* activity */,
+ drawerLayout, /* DrawerLayout object */
+ R.string.car_drawer_open,
+ R.string.car_drawer_close);
+
+ Toolbar toolbar = findViewById(R.id.car_toolbar);
+ setSupportActionBar(toolbar);
+
+ mDrawerController = new CarDrawerController(toolbar, drawerLayout, drawerToggle);
+ mDrawerController.setRootAdapter(getRootAdapter());
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setHomeButtonEnabled(true);
+ }
+
+ /**
+ * Returns the {@link CarDrawerController} that is responsible for handling events relating
+ * to the drawer in this Activity.
+ *
+ * @return The {@link CarDrawerController} linked to this Activity. This value will be
+ * {@code null} if this method is called before {@code onCreate()} has been called.
+ */
+ @Nullable
+ protected CarDrawerController getDrawerController() {
+ return mDrawerController;
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ mDrawerController.syncState();
+ }
+
+ /**
+ * @return Adapter for root content of the Drawer.
+ */
+ protected abstract CarDrawerAdapter getRootAdapter();
+
+ /**
+ * Set main content to display in this Activity. It will be added to R.id.content_frame in
+ * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(View)}.
+ *
+ * @param view View to display as main content.
+ */
+ public void setMainContent(View view) {
+ ViewGroup parent = findViewById(getContentContainerId());
+ parent.addView(view);
+ }
+
+ /**
+ * Set main content to display in this Activity. It will be added to R.id.content_frame in
+ * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(int)}.
+ *
+ * @param resourceId Layout to display as main content.
+ */
+ public void setMainContent(@LayoutRes int resourceId) {
+ ViewGroup parent = findViewById(getContentContainerId());
+ LayoutInflater inflater = getLayoutInflater();
+ inflater.inflate(resourceId, parent, true);
+ }
+
+ /**
+ * Get the id of the main content Container which is a FrameLayout. Subclasses can add their own
+ * content/fragments inside here.
+ *
+ * @return Id of FrameLayout where main content of the subclass Activity can be added.
+ */
+ protected int getContentContainerId() {
+ return R.id.content_frame;
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mDrawerController.closeDrawer();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDrawerController.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ return mDrawerController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
+ }
+}
diff --git a/android/support/car/drawer/CarDrawerAdapter.java b/android/support/car/drawer/CarDrawerAdapter.java
new file mode 100644
index 00000000..b0fd965d
--- /dev/null
+++ b/android/support/car/drawer/CarDrawerAdapter.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.support.car.drawer;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.car.R;
+import android.support.car.widget.PagedListView;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Base adapter for displaying items in the car navigation drawer, which uses a
+ * {@link PagedListView}.
+ *
+ * <p>Subclasses must set the title that will be displayed when displaying the contents of the
+ * drawer via {@link #setTitle(CharSequence)}. The title can be updated at any point later on. The
+ * title of the root adapter will also be the main title showed in the toolbar when the drawer is
+ * closed. See {@link CarDrawerController#setRootAdapter(CarDrawerAdapter)} for more information.
+ *
+ * <p>This class also takes care of implementing the PageListView.ItemCamp contract and subclasses
+ * should implement {@link #getActualItemCount()}.
+ */
+public abstract class CarDrawerAdapter extends RecyclerView.Adapter<DrawerItemViewHolder>
+ implements PagedListView.ItemCap, DrawerItemClickListener {
+ private final boolean mShowDisabledListOnEmpty;
+ private final Drawable mEmptyListDrawable;
+ private int mMaxItems = PagedListView.ItemCap.UNLIMITED;
+ private CharSequence mTitle;
+ private TitleChangeListener mTitleChangeListener;
+
+ /**
+ * Interface for a class that will be notified a new title has been set on this adapter.
+ */
+ interface TitleChangeListener {
+ /**
+ * Called when {@link #setTitle(CharSequence)} has been called and the title has been
+ * changed.
+ */
+ void onTitleChanged(CharSequence newTitle);
+ }
+
+ protected CarDrawerAdapter(Context context, boolean showDisabledListOnEmpty) {
+ mShowDisabledListOnEmpty = showDisabledListOnEmpty;
+
+ mEmptyListDrawable = context.getDrawable(R.drawable.ic_list_view_disable);
+ mEmptyListDrawable.setColorFilter(context.getColor(R.color.car_tint),
+ PorterDuff.Mode.SRC_IN);
+ }
+
+ /** Returns the title set via {@link #setTitle(CharSequence)}. */
+ CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /** Updates the title to display in the toolbar for this Adapter. */
+ public final void setTitle(@NonNull CharSequence title) {
+ if (title == null) {
+ throw new IllegalArgumentException("setTitle() cannot be passed a null title!");
+ }
+
+ mTitle = title;
+
+ if (mTitleChangeListener != null) {
+ mTitleChangeListener.onTitleChanged(mTitle);
+ }
+ }
+
+ /** Sets a listener to be notified whenever the title of this adapter has been changed. */
+ void setTitleChangeListener(@Nullable TitleChangeListener listener) {
+ mTitleChangeListener = listener;
+ }
+
+ @Override
+ public final void setMaxItems(int maxItems) {
+ mMaxItems = maxItems;
+ }
+
+ @Override
+ public final int getItemCount() {
+ if (shouldShowDisabledListItem()) {
+ return 1;
+ }
+ return mMaxItems >= 0 ? Math.min(mMaxItems, getActualItemCount()) : getActualItemCount();
+ }
+
+ /**
+ * Returns the absolute number of items that can be displayed in the list.
+ *
+ * <p>A class should implement this method to supply the number of items to be displayed.
+ * Returning 0 from this method will cause an empty list icon to be displayed in the drawer.
+ *
+ * <p>A class should override this method rather than {@link #getItemCount()} because that
+ * method is handling the logic of when to display the empty list icon. It will return 1 when
+ * {@link #getActualItemCount()} returns 0.
+ *
+ * @return The number of items to be displayed in the list.
+ */
+ protected abstract int getActualItemCount();
+
+ @Override
+ public final int getItemViewType(int position) {
+ if (shouldShowDisabledListItem()) {
+ return R.layout.car_drawer_list_item_empty;
+ }
+
+ return usesSmallLayout(position)
+ ? R.layout.car_drawer_list_item_small
+ : R.layout.car_drawer_list_item_normal;
+ }
+
+ /**
+ * Used to indicate the layout used for the Drawer item at given position. Subclasses can
+ * override this to use normal layout which includes text element below title.
+ *
+ * <p>A small layout is presented by the layout {@code R.layout.car_drawer_list_item_small}.
+ * Otherwise, the layout {@code R.layout.car_drawer_list_item_normal} will be used.
+ *
+ * @param position Adapter position of item.
+ * @return Whether the item at this position will use a small layout (default) or normal layout.
+ */
+ protected boolean usesSmallLayout(int position) {
+ return true;
+ }
+
+ @Override
+ public final DrawerItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
+ return new DrawerItemViewHolder(view);
+ }
+
+ @Override
+ public final void onBindViewHolder(DrawerItemViewHolder holder, int position) {
+ if (shouldShowDisabledListItem()) {
+ holder.getTitle().setText(null);
+ holder.getIcon().setImageDrawable(mEmptyListDrawable);
+ holder.setItemClickListener(null);
+ } else {
+ holder.setItemClickListener(this);
+ populateViewHolder(holder, position);
+ }
+ }
+
+ /**
+ * Whether or not this adapter should be displaying an empty list icon. The icon is shown if it
+ * has been configured to show and there are no items to be displayed.
+ */
+ private boolean shouldShowDisabledListItem() {
+ return mShowDisabledListOnEmpty && getActualItemCount() == 0;
+ }
+
+ /**
+ * Subclasses should set all elements in {@code holder} to populate the drawer-item. If some
+ * element is not used, it should be nulled out since these ViewHolder/View's are recycled.
+ */
+ protected abstract void populateViewHolder(DrawerItemViewHolder holder, int position);
+
+ /**
+ * Called when this adapter has been popped off the stack and is no longer needed. Subclasses
+ * can override to do any necessary cleanup.
+ */
+ public void cleanup() {}
+}
diff --git a/android/support/car/drawer/CarDrawerController.java b/android/support/car/drawer/CarDrawerController.java
new file mode 100644
index 00000000..4d9f4e99
--- /dev/null
+++ b/android/support/car/drawer/CarDrawerController.java
@@ -0,0 +1,306 @@
+/*
+ * 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.drawer;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.car.R;
+import android.support.car.widget.PagedListView;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.widget.Toolbar;
+import android.view.Gravity;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ProgressBar;
+
+import java.util.Stack;
+
+/**
+ * A controller that will handle the set up of the navigation drawer. It will hook up the
+ * necessary buttons for up navigation, as well as expose methods to allow for a drill down
+ * navigation.
+ */
+public class CarDrawerController {
+ /** The amount that the drawer has been opened before its color should be switched. */
+ private static final float COLOR_SWITCH_SLIDE_OFFSET = 0.25f;
+
+ /**
+ * A representation of the hierarchy of navigation being displayed in the list. The ordering of
+ * this stack is the order that the user has visited each level. When the user navigates up,
+ * the adapters are poopped from this list.
+ */
+ private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>();
+
+ private final Context mContext;
+
+ private final Toolbar mToolbar;
+ private final DrawerLayout mDrawerLayout;
+ private final ActionBarDrawerToggle mDrawerToggle;
+
+ private final PagedListView mDrawerList;
+ private final ProgressBar mProgressBar;
+ private final View mDrawerContent;
+
+ /**
+ * Creates a {@link CarDrawerController} that will control the navigation of the drawer given by
+ * {@code drawerLayout}.
+ *
+ * <p>The given {@code drawerLayout} should either have a child View that is inflated from
+ * {@code R.layout.car_drawer} or ensure that it three children that have the IDs found in that
+ * layout.
+ *
+ * @param toolbar The {@link Toolbar} that will serve as the action bar for an Activity.
+ * @param drawerLayout The top-level container for the window content that shows the
+ * interactive drawer.
+ * @param drawerToggle The {@link ActionBarDrawerToggle} that bridges the given {@code toolbar}
+ * and {@code drawerLayout}.
+ */
+ public CarDrawerController(Toolbar toolbar,
+ DrawerLayout drawerLayout,
+ ActionBarDrawerToggle drawerToggle) {
+ mToolbar = toolbar;
+ mContext = drawerLayout.getContext();
+
+ mDrawerLayout = drawerLayout;
+
+ mDrawerContent = drawerLayout.findViewById(R.id.drawer_content);
+ mDrawerList = drawerLayout.findViewById(R.id.drawer_list);
+ mDrawerList.setMaxPages(PagedListView.ItemCap.UNLIMITED);
+
+ mProgressBar = drawerLayout.findViewById(R.id.drawer_progress);
+
+ mDrawerToggle = drawerToggle;
+ setupDrawerToggling();
+ }
+
+ /**
+ * Sets the {@link CarDrawerAdapter} that will function as the root adapter. The contents of
+ * this root adapter are shown when the drawer is first opened. It is also the top-most level of
+ * navigation in the drawer.
+ *
+ * @param rootAdapter The adapter that will act as the root. If this value is {@code null}, then
+ * this method will do nothing.
+ */
+ public void setRootAdapter(@Nullable CarDrawerAdapter rootAdapter) {
+ if (rootAdapter == null) {
+ return;
+ }
+
+ mAdapterStack.push(rootAdapter);
+ setToolbarTitleFrom(rootAdapter);
+ mDrawerList.setAdapter(rootAdapter);
+ }
+
+ /**
+ * Switches to use the given {@link CarDrawerAdapter} as the one to supply the list to display
+ * in the navigation drawer. The title will also be updated from the adapter.
+ *
+ * <p>This switch is treated as a navigation to the next level in the drawer. Navigation away
+ * from this level will pop the given adapter off and surface contents of the previous adapter
+ * that was set via this method. If no such adapter exists, then the root adapter set by
+ * {@link #setRootAdapter(CarDrawerAdapter)} will be used instead.
+ *
+ * @param adapter Adapter for next level of content in the drawer.
+ */
+ public final void switchToAdapter(CarDrawerAdapter adapter) {
+ mAdapterStack.peek().setTitleChangeListener(null);
+ mAdapterStack.push(adapter);
+ switchToAdapterInternal(adapter);
+ }
+
+ /** Close the drawer. */
+ public void closeDrawer() {
+ if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
+ mDrawerLayout.closeDrawer(Gravity.LEFT);
+ }
+ }
+
+ /** Opens the drawer. */
+ public void openDrawer() {
+ if (!mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
+ mDrawerLayout.openDrawer(Gravity.LEFT);
+ }
+ }
+
+ /** Sets a listener to be notified of Drawer events. */
+ public void addDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
+ mDrawerLayout.addDrawerListener(listener);
+ }
+
+ /** Removes a listener to be notified of Drawer events. */
+ public void removeDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
+ mDrawerLayout.removeDrawerListener(listener);
+ }
+
+ /**
+ * Sets whether the loading progress bar is displayed in the navigation drawer. If {@code true},
+ * the progress bar is displayed and the navigation list is hidden and vice versa.
+ */
+ public void showLoadingProgressBar(boolean show) {
+ mDrawerList.setVisibility(show ? View.INVISIBLE : View.VISIBLE);
+ mProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+
+ /** Scroll to given position in the list. */
+ public void scrollToPosition(int position) {
+ mDrawerList.getRecyclerView().smoothScrollToPosition(position);
+ }
+
+ /**
+ * Retrieves the title from the given {@link CarDrawerAdapter} and set its as the title of this
+ * controller's internal Toolbar.
+ */
+ private void setToolbarTitleFrom(CarDrawerAdapter adapter) {
+ if (adapter.getTitle() == null) {
+ throw new RuntimeException("CarDrawerAdapter must supply a title via setTitle()");
+ }
+
+ mToolbar.setTitle(adapter.getTitle());
+ adapter.setTitleChangeListener(mToolbar::setTitle);
+ }
+
+ /**
+ * Sets up the necessary listeners for {@link DrawerLayout} so that the navigation drawer
+ * hierarchy is properly displayed.
+ */
+ private void setupDrawerToggling() {
+ mDrawerLayout.addDrawerListener(mDrawerToggle);
+ mDrawerLayout.addDrawerListener(
+ new DrawerLayout.DrawerListener() {
+ @Override
+ public void onDrawerSlide(View drawerView, float slideOffset) {
+ // Correctly set the title and arrow colors as they are different between
+ // the open and close states.
+ updateTitleAndArrowColor(slideOffset >= COLOR_SWITCH_SLIDE_OFFSET);
+ }
+
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ // If drawer is closed, revert stack/drawer to initial root state.
+ cleanupStackAndShowRoot();
+ scrollToPosition(0);
+ }
+
+ @Override
+ public void onDrawerOpened(View drawerView) {}
+
+ @Override
+ public void onDrawerStateChanged(int newState) {}
+ });
+ }
+
+ /** Sets the title and arrow color of the drawer depending on if it is open or not. */
+ private void updateTitleAndArrowColor(boolean drawerOpen) {
+ // When the drawer is open, use car_title, which resolves to appropriate color depending on
+ // day-night mode. When drawer is closed, we always use light color.
+ int titleColorResId = drawerOpen ? R.color.car_title : R.color.car_title_light;
+ int titleColor = mContext.getColor(titleColorResId);
+ mToolbar.setTitleTextColor(titleColor);
+ mDrawerToggle.getDrawerArrowDrawable().setColor(titleColor);
+ }
+
+ /**
+ * Synchronizes the display of the drawer with its linked {@link DrawerLayout}.
+ *
+ * <p>This should be called from the associated Activity's
+ * {@link android.support.v7.app.AppCompatActivity#onPostCreate(Bundle)} method to synchronize
+ * after teh DRawerLayout's instance state has been restored, and any other time when the
+ * state may have diverged in such a way that this controller's associated
+ * {@link ActionBarDrawerToggle} had not been notified.
+ */
+ public void syncState() {
+ mDrawerToggle.syncState();
+
+ // In case we're restarting after a config change (e.g. day, night switch), set colors
+ // again. Doing it here so that Drawer state is fully synced and we know if its open or not.
+ // NOTE: isDrawerOpen must be passed the second child of the DrawerLayout.
+ updateTitleAndArrowColor(mDrawerLayout.isDrawerOpen(mDrawerContent));
+ }
+
+ /**
+ * Notify this controller that device configurations may have changed.
+ *
+ * <p>This method should be called from the associated Activity's
+ * {@code onConfigurationChanged()} method.
+ */
+ public void onConfigurationChanged(Configuration newConfig) {
+ // Pass any configuration change to the drawer toggle.
+ mDrawerToggle.onConfigurationChanged(newConfig);
+ }
+
+ /**
+ * An analog to an Activity's {@code onOptionsItemSelected()}. This method should be called
+ * when the Activity's method is called and will return {@code true} if the selection has
+ * been handled.
+ *
+ * @return {@code true} if the item processing was handled by this class.
+ */
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle home-click and see if we can navigate up in the drawer.
+ if (item != null && item.getItemId() == android.R.id.home && maybeHandleUpClick()) {
+ return true;
+ }
+
+ // DrawerToggle gets next chance to handle up-clicks (and any other clicks).
+ return mDrawerToggle.onOptionsItemSelected(item);
+ }
+
+ /**
+ * Sets the navigation drawer's title to be the one supplied by the given adapter and updates
+ * the navigation drawer list with the adapter's contents.
+ */
+ private void switchToAdapterInternal(CarDrawerAdapter adapter) {
+ setToolbarTitleFrom(adapter);
+ // NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between
+ // car_drawer_list_item_normal, car_drawer_list_item_small and car_list_empty layouts.
+ mDrawerList.getRecyclerView().setAdapter(adapter);
+ scrollToPosition(0);
+ }
+
+ /**
+ * Switches to the previous level in the drawer hierarchy if the current list being displayed
+ * is not the root adapter. This is analogous to a navigate up.
+ *
+ * @return {@code true} if a navigate up was possible and executed. {@code false} otherwise.
+ */
+ private boolean maybeHandleUpClick() {
+ // Check if already at the root level.
+ if (mAdapterStack.size() <= 1) {
+ return false;
+ }
+
+ CarDrawerAdapter adapter = mAdapterStack.pop();
+ adapter.setTitleChangeListener(null);
+ adapter.cleanup();
+ switchToAdapterInternal(mAdapterStack.peek());
+ return true;
+ }
+
+ /** Clears stack down to root adapter and switches to root adapter. */
+ private void cleanupStackAndShowRoot() {
+ while (mAdapterStack.size() > 1) {
+ CarDrawerAdapter adapter = mAdapterStack.pop();
+ adapter.setTitleChangeListener(null);
+ adapter.cleanup();
+ }
+ switchToAdapterInternal(mAdapterStack.peek());
+ }
+}
diff --git a/android/util/StatsLogTag.java b/android/support/car/drawer/DrawerItemClickListener.java
index 5e5a8287..d707dbd0 100644
--- a/android/util/StatsLogTag.java
+++ b/android/support/car/drawer/DrawerItemClickListener.java
@@ -14,19 +14,16 @@
* 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;
+package android.support.car.drawer;
+/**
+ * Listener for handling clicks on items/views managed by {@link DrawerItemViewHolder}.
+ */
+public interface DrawerItemClickListener {
+ /**
+ * Callback when item is clicked.
+ *
+ * @param position Adapter position of the clicked item.
+ */
+ void onItemClick(int position);
}
diff --git a/android/support/car/drawer/DrawerItemViewHolder.java b/android/support/car/drawer/DrawerItemViewHolder.java
new file mode 100644
index 00000000..d016b2de
--- /dev/null
+++ b/android/support/car/drawer/DrawerItemViewHolder.java
@@ -0,0 +1,87 @@
+/*
+ * 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.drawer;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.car.R;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Re-usable {@link RecyclerView.ViewHolder} for displaying items in the
+ * {@link android.support.car.drawer.CarDrawerAdapter}.
+ */
+public class DrawerItemViewHolder extends RecyclerView.ViewHolder {
+ private final ImageView mIcon;
+ private final TextView mTitle;
+ private final TextView mText;
+ private final ImageView mEndIcon;
+
+ DrawerItemViewHolder(View view) {
+ super(view);
+ mIcon = view.findViewById(R.id.icon);
+ if (mIcon == null) {
+ throw new IllegalArgumentException("Icon view cannot be null!");
+ }
+
+ mTitle = view.findViewById(R.id.title);
+ if (mTitle == null) {
+ throw new IllegalArgumentException("Title view cannot be null!");
+ }
+
+ // Next two are optional and may be null.
+ mText = view.findViewById(R.id.text);
+ mEndIcon = view.findViewById(R.id.end_icon);
+ }
+
+ /** Returns the view that should be used to display the main icon. */
+ @NonNull
+ public ImageView getIcon() {
+ return mIcon;
+ }
+
+ /** Returns the view that will display the main title. */
+ @NonNull
+ public TextView getTitle() {
+ return mTitle;
+ }
+
+ /** Returns the view that is used for text that is smaller than the title text. */
+ @Nullable
+ public TextView getText() {
+ return mText;
+ }
+
+ /** Returns the icon that is displayed at the end of the view. */
+ @Nullable
+ public ImageView getEndIcon() {
+ return mEndIcon;
+ }
+
+ /**
+ * Sets the listener that will be notified when the view held by this ViewHolder has been
+ * clicked. Passing {@code null} will clear any previously set listeners.
+ */
+ void setItemClickListener(@Nullable DrawerItemClickListener listener) {
+ itemView.setOnClickListener(listener != null
+ ? v -> listener.onItemClick(getAdapterPosition())
+ : null);
+ }
+}
diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java
index 8527c659..46527001 100644
--- a/android/support/car/widget/PagedListView.java
+++ b/android/support/car/widget/PagedListView.java
@@ -27,7 +27,6 @@ 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;
@@ -61,7 +60,6 @@ public class PagedListView extends FrameLayout {
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;
@@ -98,6 +96,11 @@ public class PagedListView extends FrameLayout {
*/
public interface ItemCap {
/**
+ * A value to pass to {@link #setMaxItems(int)} that indicates there should be no limit.
+ */
+ int UNLIMITED = -1;
+
+ /**
* Sets the maximum number of items available in the adapter. A value less than '0' means
* the list should not be capped.
*/
@@ -139,7 +142,6 @@ public class PagedListView extends FrameLayout {
}
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);
@@ -156,6 +158,16 @@ public class PagedListView extends FrameLayout {
mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12);
mRecyclerView.setItemAnimator(new CarItemAnimator(mLayoutManager));
+ boolean offsetScrollBar = a.getBoolean(R.styleable.PagedListView_offsetScrollBar, false);
+ if (offsetScrollBar) {
+ MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams();
+ params.setMarginStart(getResources().getDimensionPixelSize(
+ R.dimen.car_screen_margin_size));
+ params.setMarginEnd(
+ a.getDimensionPixelSize(R.styleable.PagedListView_listEndMargin, 0));
+ mRecyclerView.setLayoutParams(params);
+ }
+
if (a.getBoolean(R.styleable.PagedListView_showPagedListViewDivider, true)) {
int dividerStartMargin = a.getDimensionPixelSize(
R.styleable.PagedListView_dividerStartMargin, 0);
@@ -199,47 +211,20 @@ public class PagedListView extends FrameLayout {
}
}
});
+
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);
+ // Modify the layout the Scroll Bar is not visible.
+ if (!mScrollBarEnabled) {
+ MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams();
+ params.setMarginStart(0);
+ mRecyclerView.setLayoutParams(params);
}
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();
diff --git a/android/support/media/tv/BasePreviewProgram.java b/android/support/media/tv/BasePreviewProgram.java
index 1423d9d6..39c30140 100644
--- a/android/support/media/tv/BasePreviewProgram.java
+++ b/android/support/media/tv/BasePreviewProgram.java
@@ -23,14 +23,13 @@ import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
+import android.support.annotation.IntDef;
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.PreviewProgramColumns;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.AspectRatio;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.Availability;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.InteractionType;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.Type;
import android.support.media.tv.TvContractCompat.PreviewPrograms;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -55,6 +54,89 @@ public abstract class BasePreviewProgram extends BaseProgram {
private static final int IS_LIVE = 1;
private static final int IS_BROWSABLE = 1;
+ /** @hide */
+ @IntDef({
+ TYPE_UNKNOWN,
+ PreviewProgramColumns.TYPE_MOVIE,
+ PreviewProgramColumns.TYPE_TV_SERIES,
+ PreviewProgramColumns.TYPE_TV_SEASON,
+ PreviewProgramColumns.TYPE_TV_EPISODE,
+ PreviewProgramColumns.TYPE_CLIP,
+ PreviewProgramColumns.TYPE_EVENT,
+ PreviewProgramColumns.TYPE_CHANNEL,
+ PreviewProgramColumns.TYPE_TRACK,
+ PreviewProgramColumns.TYPE_ALBUM,
+ PreviewProgramColumns.TYPE_ARTIST,
+ PreviewProgramColumns.TYPE_PLAYLIST,
+ PreviewProgramColumns.TYPE_STATION,
+ PreviewProgramColumns.TYPE_GAME
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface Type {}
+
+ /**
+ * The unknown program type.
+ */
+ private static final int TYPE_UNKNOWN = -1;
+
+ /** @hide */
+ @IntDef({
+ ASPECT_RATIO_UNKNOWN,
+ PreviewProgramColumns.ASPECT_RATIO_16_9,
+ PreviewProgramColumns.ASPECT_RATIO_3_2,
+ PreviewProgramColumns.ASPECT_RATIO_4_3,
+ PreviewProgramColumns.ASPECT_RATIO_1_1,
+ PreviewProgramColumns.ASPECT_RATIO_2_3,
+ PreviewProgramColumns.ASPECT_RATIO_MOVIE_POSTER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface AspectRatio {}
+
+ /**
+ * The aspect ratio for unknown aspect ratios.
+ */
+ private static final int ASPECT_RATIO_UNKNOWN = -1;
+
+ /** @hide */
+ @IntDef({
+ AVAILABILITY_UNKNOWN,
+ PreviewProgramColumns.AVAILABILITY_AVAILABLE,
+ PreviewProgramColumns.AVAILABILITY_FREE_WITH_SUBSCRIPTION,
+ PreviewProgramColumns.AVAILABILITY_PAID_CONTENT,
+ PreviewProgramColumns.AVAILABILITY_PURCHASED,
+ PreviewProgramColumns.AVAILABILITY_FREE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface Availability {}
+
+ /**
+ * The unknown availability.
+ */
+ private static final int AVAILABILITY_UNKNOWN = -1;
+
+ /** @hide */
+ @IntDef({
+ INTERACTION_TYPE_UNKNOWN,
+ PreviewProgramColumns.INTERACTION_TYPE_VIEWS,
+ PreviewProgramColumns.INTERACTION_TYPE_LISTENS,
+ PreviewProgramColumns.INTERACTION_TYPE_FOLLOWERS,
+ PreviewProgramColumns.INTERACTION_TYPE_FANS,
+ PreviewProgramColumns.INTERACTION_TYPE_LIKES,
+ PreviewProgramColumns.INTERACTION_TYPE_THUMBS,
+ PreviewProgramColumns.INTERACTION_TYPE_VIEWERS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface InteractionType {}
+
+ /**
+ * The unknown interaction type.
+ */
+ private static final int INTERACTION_TYPE_UNKNOWN = -1;
+
BasePreviewProgram(Builder builder) {
super(builder);
}
@@ -127,7 +209,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @Type int getType() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_TYPE);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? TYPE_UNKNOWN : i;
}
/**
@@ -137,7 +219,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @AspectRatio int getPosterArtAspectRatio() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? ASPECT_RATIO_UNKNOWN : i;
}
/**
@@ -147,7 +229,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @AspectRatio int getThumbnailAspectRatio() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? ASPECT_RATIO_UNKNOWN : i;
}
/**
@@ -165,7 +247,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @Availability int getAvailability() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_AVAILABILITY);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? AVAILABILITY_UNKNOWN : i;
}
/**
@@ -216,7 +298,7 @@ public abstract class BasePreviewProgram extends BaseProgram {
*/
public @InteractionType int getInteractionType() {
Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_INTERACTION_TYPE);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? INTERACTION_TYPE_UNKNOWN : i;
}
/**
diff --git a/android/support/media/tv/BaseProgram.java b/android/support/media/tv/BaseProgram.java
index e4ce9d1f..23b5cf9c 100644
--- a/android/support/media/tv/BaseProgram.java
+++ b/android/support/media/tv/BaseProgram.java
@@ -22,13 +22,16 @@ import android.database.Cursor;
import android.media.tv.TvContentRating;
import android.net.Uri;
import android.os.Build;
+import android.support.annotation.IntDef;
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.BaseTvColumns;
import android.support.media.tv.TvContractCompat.ProgramColumns;
-import android.support.media.tv.TvContractCompat.ProgramColumns.ReviewRatingStyle;
import android.support.media.tv.TvContractCompat.Programs;
import android.support.media.tv.TvContractCompat.Programs.Genres.Genre;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Base class for derived classes that want to have common fields for programs defined in
* {@link TvContractCompat}.
@@ -46,6 +49,22 @@ public abstract class BaseProgram {
private static final int IS_SEARCHABLE = 1;
/** @hide */
+ @IntDef({
+ REVIEW_RATING_STYLE_UNKNOWN,
+ ProgramColumns.REVIEW_RATING_STYLE_STARS,
+ ProgramColumns.REVIEW_RATING_STYLE_THUMBS_UP_DOWN,
+ ProgramColumns.REVIEW_RATING_STYLE_PERCENTAGE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ @interface ReviewRatingStyle {}
+
+ /**
+ * The unknown review rating style.
+ */
+ private static final int REVIEW_RATING_STYLE_UNKNOWN = -1;
+
+ /** @hide */
@RestrictTo(LIBRARY_GROUP)
protected ContentValues mValues;
@@ -254,7 +273,7 @@ public abstract class BaseProgram {
*/
public @ReviewRatingStyle int getReviewRatingStyle() {
Integer i = mValues.getAsInteger(Programs.COLUMN_REVIEW_RATING_STYLE);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? REVIEW_RATING_STYLE_UNKNOWN : i;
}
/**
diff --git a/android/support/media/tv/Program.java b/android/support/media/tv/Program.java
index 4e3bd7ac..233f1bab 100644
--- a/android/support/media/tv/Program.java
+++ b/android/support/media/tv/Program.java
@@ -25,6 +25,7 @@ import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.Programs;
+import android.support.media.tv.TvContractCompat.Programs.Genres.Genre;
/**
* A convenience class to access {@link TvContractCompat.Programs} entries in the system content
@@ -282,7 +283,7 @@ public final class Program extends BaseProgram implements Comparable<Program> {
* @return This Builder object to allow for chaining of calls to builder methods.
* @see Programs#COLUMN_BROADCAST_GENRE
*/
- public Builder setBroadcastGenres(String[] genres) {
+ public Builder setBroadcastGenres(@Genre String[] genres) {
mValues.put(Programs.COLUMN_BROADCAST_GENRE, Programs.Genres.encode(genres));
return this;
}
diff --git a/android/support/media/tv/TvContractCompat.java b/android/support/media/tv/TvContractCompat.java
index 5a46e791..de4fd04f 100644
--- a/android/support/media/tv/TvContractCompat.java
+++ b/android/support/media/tv/TvContractCompat.java
@@ -30,7 +30,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.BaseColumns;
-import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
@@ -606,16 +605,6 @@ public final class TvContractCompat {
*/
@RestrictTo(LIBRARY_GROUP)
interface ProgramColumns {
- /** @hide */
- @IntDef({
- REVIEW_RATING_STYLE_STARS,
- REVIEW_RATING_STYLE_THUMBS_UP_DOWN,
- REVIEW_RATING_STYLE_PERCENTAGE,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- @interface ReviewRatingStyle {}
-
/**
* The review rating style for five star rating.
*
@@ -934,27 +923,6 @@ public final class TvContractCompat {
*/
@RestrictTo(LIBRARY_GROUP)
public interface PreviewProgramColumns {
-
- /** @hide */
- @IntDef({
- TYPE_MOVIE,
- TYPE_TV_SERIES,
- TYPE_TV_SEASON,
- TYPE_TV_EPISODE,
- TYPE_CLIP,
- TYPE_EVENT,
- TYPE_CHANNEL,
- TYPE_TRACK,
- TYPE_ALBUM,
- TYPE_ARTIST,
- TYPE_PLAYLIST,
- TYPE_STATION,
- TYPE_GAME
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface Type {}
-
/**
* The program type for movie.
*
@@ -1046,19 +1014,6 @@ public final class TvContractCompat {
*/
int TYPE_GAME = 12;
- /** @hide */
- @IntDef({
- ASPECT_RATIO_16_9,
- ASPECT_RATIO_3_2,
- ASPECT_RATIO_4_3,
- ASPECT_RATIO_1_1,
- ASPECT_RATIO_2_3,
- ASPECT_RATIO_MOVIE_POSTER,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface AspectRatio {}
-
/**
* The aspect ratio for 16:9.
*
@@ -1107,18 +1062,6 @@ public final class TvContractCompat {
*/
int ASPECT_RATIO_MOVIE_POSTER = 5;
- /** @hide */
- @IntDef({
- AVAILABILITY_AVAILABLE,
- AVAILABILITY_FREE_WITH_SUBSCRIPTION,
- AVAILABILITY_PAID_CONTENT,
- AVAILABILITY_PURCHASED,
- AVAILABILITY_FREE,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface Availability {}
-
/**
* The availability for "available to this user".
*
@@ -1155,20 +1098,6 @@ public final class TvContractCompat {
*/
int AVAILABILITY_FREE = 4;
- /** @hide */
- @IntDef({
- INTERACTION_TYPE_VIEWS,
- INTERACTION_TYPE_LISTENS,
- INTERACTION_TYPE_FOLLOWERS,
- INTERACTION_TYPE_FANS,
- INTERACTION_TYPE_LIKES,
- INTERACTION_TYPE_THUMBS,
- INTERACTION_TYPE_VIEWERS,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface InteractionType {}
-
/**
* The interaction type for "views".
*
@@ -2895,17 +2824,6 @@ public final class TvContractCompat {
/** The MIME type of a single preview TV program. */
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/watch_next_program";
- /** @hide */
- @IntDef({
- WATCH_NEXT_TYPE_CONTINUE,
- WATCH_NEXT_TYPE_NEXT,
- WATCH_NEXT_TYPE_NEW,
- WATCH_NEXT_TYPE_WATCHLIST,
- })
- @Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP)
- public @interface WatchNextType {}
-
/**
* The watch next type for CONTINUE. Use this type when the user has already watched more
* than 1 minute of this content.
diff --git a/android/support/media/tv/WatchNextProgram.java b/android/support/media/tv/WatchNextProgram.java
index f4665846..c192745c 100644
--- a/android/support/media/tv/WatchNextProgram.java
+++ b/android/support/media/tv/WatchNextProgram.java
@@ -22,12 +22,15 @@ import android.content.ContentValues;
import android.database.Cursor;
import android.media.tv.TvContentRating; // For javadoc gen of super class
import android.os.Build;
+import android.support.annotation.IntDef;
import android.support.annotation.RestrictTo;
import android.support.media.tv.TvContractCompat.PreviewPrograms; // For javadoc gen of super class
import android.support.media.tv.TvContractCompat.Programs; // For javadoc gen of super class
import android.support.media.tv.TvContractCompat.Programs.Genres; // For javadoc gen of super class
import android.support.media.tv.TvContractCompat.WatchNextPrograms;
-import android.support.media.tv.TvContractCompat.WatchNextPrograms.WatchNextType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* A convenience class to access {@link WatchNextPrograms} entries in the system content
@@ -87,16 +90,34 @@ public final class WatchNextProgram extends BasePreviewProgram {
private static final long INVALID_LONG_VALUE = -1;
private static final int INVALID_INT_VALUE = -1;
+ /** @hide */
+ @IntDef({
+ WATCH_NEXT_TYPE_UNKNOWN,
+ WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE,
+ WatchNextPrograms.WATCH_NEXT_TYPE_NEXT,
+ WatchNextPrograms.WATCH_NEXT_TYPE_NEW,
+ WatchNextPrograms.WATCH_NEXT_TYPE_WATCHLIST,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP)
+ public @interface WatchNextType {}
+
+ /**
+ * The unknown watch next type. Use this type when the actual type is not known.
+ */
+ public static final int WATCH_NEXT_TYPE_UNKNOWN = -1;
+
private WatchNextProgram(Builder builder) {
super(builder);
}
/**
- * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program.
+ * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program,
+ * or {@link #WATCH_NEXT_TYPE_UNKNOWN} if it's unknown.
*/
public @WatchNextType int getWatchNextType() {
Integer i = mValues.getAsInteger(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE);
- return i == null ? INVALID_INT_VALUE : i;
+ return i == null ? WATCH_NEXT_TYPE_UNKNOWN : i;
}
/**
diff --git a/android/support/mediacompat/testlib/IntentConstants.java b/android/support/mediacompat/testlib/IntentConstants.java
index bc35935e..57db43e7 100644
--- a/android/support/mediacompat/testlib/IntentConstants.java
+++ b/android/support/mediacompat/testlib/IntentConstants.java
@@ -22,6 +22,8 @@ package android.support.mediacompat.testlib;
public class IntentConstants {
public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
"android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
+ public static final String ACTION_CALL_MEDIA_SESSION_METHOD =
+ "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD";
public static final String KEY_METHOD_ID = "method_id";
public static final String KEY_ARGUMENT = "argument";
}
diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java
new file mode 100644
index 00000000..82b5c59a
--- /dev/null
+++ b/android/support/mediacompat/testlib/MediaSessionConstants.java
@@ -0,0 +1,57 @@
+/*
+ * 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.mediacompat.testlib;
+
+/**
+ * Constants for testing the media session and controller.
+ */
+public class MediaSessionConstants {
+
+ // MediaSessionCompat methods.
+ public static final int SET_EXTRAS = 101;
+ public static final int SET_FLAGS = 102;
+ public static final int SET_METADATA = 103;
+ public static final int SET_PLAYBACK_STATE = 104;
+ public static final int SET_QUEUE = 105;
+ public static final int SET_QUEUE_TITLE = 106;
+ public static final int SET_SESSION_ACTIVITY = 107;
+ public static final int SET_CAPTIONING_ENABLED = 108;
+ public static final int SET_REPEAT_MODE = 109;
+ public static final int SET_SHUFFLE_MODE = 110;
+ public static final int SEND_SESSION_EVENT = 112;
+ public static final int SET_ACTIVE = 113;
+ public static final int RELEASE = 114;
+ public static final int SET_PLAYBACK_TO_LOCAL = 115;
+ public static final int SET_PLAYBACK_TO_REMOTE = 116;
+ public static final int SET_RATING_TYPE = 117;
+
+ public static final String SERVICE_PACKAGE_NAME = "android.support.mediacompat.service.test";
+ public static final String TEST_KEY = "test-key";
+ public static final String TEST_VALUE = "test-val";
+ public static final String TEST_SESSION_EVENT = "test-session-event";
+ public static final int TEST_FLAGS = 5;
+ public static final int TEST_CURRENT_VOLUME = 10;
+ public static final int TEST_MAX_VOLUME = 11;
+ public static final long TEST_QUEUE_ID_1 = 10L;
+ public static final long TEST_QUEUE_ID_2 = 20L;
+ public static final String TEST_MEDIA_ID_1 = "media_id_1";
+ public static final String TEST_MEDIA_ID_2 = "media_id_2";
+ public static final long TEST_ACTION = 55L;
+
+ public static final int TEST_ERROR_CODE = 0x3;
+ public static final String TEST_ERROR_MSG = "test-error-msg";
+}
diff --git a/android/support/transition/AutoTransition.java b/android/support/transition/AutoTransition.java
index 02b49e26..bf39c3c3 100644
--- a/android/support/transition/AutoTransition.java
+++ b/android/support/transition/AutoTransition.java
@@ -45,9 +45,9 @@ public class AutoTransition extends TransitionSet {
private void init() {
setOrdering(ORDERING_SEQUENTIAL);
- addTransition(new Fade(Fade.OUT)).
- addTransition(new ChangeBounds()).
- addTransition(new Fade(Fade.IN));
+ addTransition(new Fade(Fade.OUT))
+ .addTransition(new ChangeBounds())
+ .addTransition(new Fade(Fade.IN));
}
}
diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java
index 81431972..af37f77a 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -2240,10 +2240,24 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
focusToViewInLayout(hadFocus, scrollToFocus, -deltaPrimary, -deltaSecondary);
appendVisibleItems();
prependVisibleItems();
- removeInvisibleViewsAtFront();
- removeInvisibleViewsAtEnd();
+ // b/67370222: do not removeInvisibleViewsAtFront/End() in the loop, otherwise
+ // loop may bounce between scroll forward and scroll backward forever. Example:
+ // Assuming there are 19 items, child#18 and child#19 are both in RV, we are
+ // trying to focus to child#18 and there are 200px remaining scroll distance.
+ // 1 focusToViewInLayout() tries scroll forward 50 px to align focused child#18 on
+ // right edge, but there to compensate remaining scroll 200px, also scroll
+ // backward 200px, 150px pushes last child#19 out side of right edge.
+ // 2 removeInvisibleViewsAtEnd() remove last child#19, updateScrollLimits()
+ // invalidates scroll max
+ // 3 In next iteration, when scroll max/min is unknown, focusToViewInLayout() will
+ // align focused child#18 at center of screen.
+ // 4 Because #18 is aligned at center, appendVisibleItems() will fill child#19 to
+ // the right.
+ // 5 (back to 1 and loop forever)
} while (mGrid.getFirstVisibleIndex() != oldFirstVisible
|| mGrid.getLastVisibleIndex() != oldLastVisible);
+ removeInvisibleViewsAtFront();
+ removeInvisibleViewsAtEnd();
if (state.willRunPredictiveAnimations()) {
fillScrapViewsInPostLayout();
diff --git a/android/support/v4/content/res/ResourcesCompat.java b/android/support/v4/content/res/ResourcesCompat.java
index 4c70ce93..15b8ce9a 100644
--- a/android/support/v4/content/res/ResourcesCompat.java
+++ b/android/support/v4/content/res/ResourcesCompat.java
@@ -307,11 +307,11 @@ public final class ResourcesCompat {
*/
@RestrictTo(LIBRARY_GROUP)
public static Typeface getFont(@NonNull Context context, @FontRes int id, TypedValue value,
- int style) throws NotFoundException {
+ int style, @Nullable FontCallback fontCallback) throws NotFoundException {
if (context.isRestricted()) {
return null;
}
- return loadFont(context, id, value, style, null /* callback */, null /* handler */,
+ return loadFont(context, id, value, style, fontCallback, null /* handler */,
true /* isXmlRequest */);
}
diff --git a/android/support/v4/media/RatingCompat.java b/android/support/v4/media/RatingCompat.java
index b538cac4..e70243f8 100644
--- a/android/support/v4/media/RatingCompat.java
+++ b/android/support/v4/media/RatingCompat.java
@@ -18,6 +18,7 @@ package android.support.v4.media;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import android.media.Rating;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -326,25 +327,25 @@ public final class RatingCompat implements Parcelable {
*/
public static RatingCompat fromRating(Object ratingObj) {
if (ratingObj != null && Build.VERSION.SDK_INT >= 19) {
- final int ratingStyle = RatingCompatKitkat.getRatingStyle(ratingObj);
+ final int ratingStyle = ((Rating) ratingObj).getRatingStyle();
final RatingCompat rating;
- if (RatingCompatKitkat.isRated(ratingObj)) {
+ if (((Rating) ratingObj).isRated()) {
switch (ratingStyle) {
case RATING_HEART:
- rating = newHeartRating(RatingCompatKitkat.hasHeart(ratingObj));
+ rating = newHeartRating(((Rating) ratingObj).hasHeart());
break;
case RATING_THUMB_UP_DOWN:
- rating = newThumbRating(RatingCompatKitkat.isThumbUp(ratingObj));
+ rating = newThumbRating(((Rating) ratingObj).isThumbUp());
break;
case RATING_3_STARS:
case RATING_4_STARS:
case RATING_5_STARS:
rating = newStarRating(ratingStyle,
- RatingCompatKitkat.getStarRating(ratingObj));
+ ((Rating) ratingObj).getStarRating());
break;
case RATING_PERCENTAGE:
rating = newPercentageRating(
- RatingCompatKitkat.getPercentRating(ratingObj));
+ ((Rating) ratingObj).getPercentRating());
break;
default:
return null;
@@ -372,25 +373,25 @@ public final class RatingCompat implements Parcelable {
if (isRated()) {
switch (mRatingStyle) {
case RATING_HEART:
- mRatingObj = RatingCompatKitkat.newHeartRating(hasHeart());
+ mRatingObj = Rating.newHeartRating(hasHeart());
break;
case RATING_THUMB_UP_DOWN:
- mRatingObj = RatingCompatKitkat.newThumbRating(isThumbUp());
+ mRatingObj = Rating.newThumbRating(isThumbUp());
break;
case RATING_3_STARS:
case RATING_4_STARS:
case RATING_5_STARS:
- mRatingObj = RatingCompatKitkat.newStarRating(mRatingStyle,
+ mRatingObj = Rating.newStarRating(mRatingStyle,
getStarRating());
break;
case RATING_PERCENTAGE:
- mRatingObj = RatingCompatKitkat.newPercentageRating(getPercentRating());
+ mRatingObj = Rating.newPercentageRating(getPercentRating());
break;
default:
return null;
}
} else {
- mRatingObj = RatingCompatKitkat.newUnratedRating(mRatingStyle);
+ mRatingObj = Rating.newUnratedRating(mRatingStyle);
}
}
return mRatingObj;
diff --git a/android/support/v4/media/RatingCompatKitkat.java b/android/support/v4/media/RatingCompatKitkat.java
deleted file mode 100644
index 1d3fa505..00000000
--- a/android/support/v4/media/RatingCompatKitkat.java
+++ /dev/null
@@ -1,67 +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.v4.media;
-
-import android.media.Rating;
-import android.support.annotation.RequiresApi;
-
-@RequiresApi(19)
-class RatingCompatKitkat {
- public static Object newUnratedRating(int ratingStyle) {
- return Rating.newUnratedRating(ratingStyle);
- }
-
- public static Object newHeartRating(boolean hasHeart) {
- return Rating.newHeartRating(hasHeart);
- }
-
- public static Object newThumbRating(boolean thumbIsUp) {
- return Rating.newThumbRating(thumbIsUp);
- }
-
- public static Object newStarRating(int starRatingStyle, float starRating) {
- return Rating.newStarRating(starRatingStyle, starRating);
- }
-
- public static Object newPercentageRating(float percent) {
- return Rating.newPercentageRating(percent);
- }
-
- public static boolean isRated(Object ratingObj) {
- return ((Rating)ratingObj).isRated();
- }
-
- public static int getRatingStyle(Object ratingObj) {
- return ((Rating)ratingObj).getRatingStyle();
- }
-
- public static boolean hasHeart(Object ratingObj) {
- return ((Rating)ratingObj).hasHeart();
- }
-
- public static boolean isThumbUp(Object ratingObj) {
- return ((Rating)ratingObj).isThumbUp();
- }
-
- public static float getStarRating(Object ratingObj) {
- return ((Rating)ratingObj).getStarRating();
- }
-
- public static float getPercentRating(Object ratingObj) {
- return ((Rating)ratingObj).getPercentRating();
- }
-}
diff --git a/android/support/v4/provider/FontsContractCompat.java b/android/support/v4/provider/FontsContractCompat.java
index 9ef1b0b0..09261869 100644
--- a/android/support/v4/provider/FontsContractCompat.java
+++ b/android/support/v4/provider/FontsContractCompat.java
@@ -303,6 +303,9 @@ public class FontsContractCompat {
final ArrayList<ReplyCallback<TypefaceResult>> replies;
synchronized (sLock) {
replies = sPendingReplies.get(id);
+ if (replies == null) {
+ return; // Nobody requested replies. Do nothing.
+ }
sPendingReplies.remove(id);
}
for (int i = 0; i < replies.size(); ++i) {
diff --git a/android/support/v7/app/MediaRouteButton.java b/android/support/v7/app/MediaRouteButton.java
index d3f7020b..fdbcf9ad 100644
--- a/android/support/v7/app/MediaRouteButton.java
+++ b/android/support/v7/app/MediaRouteButton.java
@@ -121,8 +121,7 @@ public class MediaRouteButton extends View {
}
public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
- super(MediaRouterThemeHelper.createThemedContext(context, defStyleAttr), attrs,
- defStyleAttr);
+ super(MediaRouterThemeHelper.createThemedButtonContext(context), attrs, defStyleAttr);
context = getContext();
mRouter = MediaRouter.getInstance(context);
diff --git a/android/support/v7/app/MediaRouteChooserDialog.java b/android/support/v7/app/MediaRouteChooserDialog.java
index 0ab2eb11..17364efb 100644
--- a/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/android/support/v7/app/MediaRouteChooserDialog.java
@@ -92,10 +92,8 @@ public class MediaRouteChooserDialog extends AppCompatDialog {
}
public MediaRouteChooserDialog(Context context, int theme) {
- // If we pass theme ID of 0 to AppCompatDialog, it will apply dialogTheme on the context,
- // which may override our style settings. Passes our uppermost theme ID to prevent this.
- super(MediaRouterThemeHelper.createThemedContext(context, theme),
- theme == 0 ? MediaRouterThemeHelper.createThemeForDialog(context, theme) : theme);
+ super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, false),
+ MediaRouterThemeHelper.createThemedDialogStyle(context));
context = getContext();
mRouter = MediaRouter.getInstance(context);
diff --git a/android/support/v7/app/MediaRouteControllerDialog.java b/android/support/v7/app/MediaRouteControllerDialog.java
index 4b9a17a3..d89bf21e 100644
--- a/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/android/support/v7/app/MediaRouteControllerDialog.java
@@ -201,12 +201,8 @@ public class MediaRouteControllerDialog extends AlertDialog {
}
public MediaRouteControllerDialog(Context context, int theme) {
- // If we pass theme ID of 0 to AppCompatDialog, it will apply dialogTheme on the context,
- // which may override our style settings. Passes our uppermost theme ID to prevent this.
- super(MediaRouterThemeHelper.createThemedContext(context,
- MediaRouterThemeHelper.getAlertDialogResolvedTheme(context, theme)), theme == 0
- ? MediaRouterThemeHelper.createThemeForDialog(context, MediaRouterThemeHelper
- .getAlertDialogResolvedTheme(context, theme)) : theme);
+ super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, true),
+ MediaRouterThemeHelper.createThemedDialogStyle(context));
mContext = getContext();
mControllerCallback = new MediaControllerCallback();
diff --git a/android/support/v7/app/MediaRouterThemeHelper.java b/android/support/v7/app/MediaRouterThemeHelper.java
index 9ef218e0..69e40ac7 100644
--- a/android/support/v7/app/MediaRouterThemeHelper.java
+++ b/android/support/v7/app/MediaRouterThemeHelper.java
@@ -42,47 +42,76 @@ final class MediaRouterThemeHelper {
private MediaRouterThemeHelper() {
}
- /**
- * Creates a themed context based on the explicit style resource or the parent context's default
- * theme.
- * <p>
- * The theme which will be applied on top of the parent {@code context}'s theme is determined
- * by the primary color defined in the given {@code style}, or in the parent {@code context}.
+ static Context createThemedButtonContext(Context context) {
+ // Apply base Media Router theme.
+ context = new ContextThemeWrapper(context, getRouterThemeId(context));
+
+ // Apply custom Media Router theme.
+ int style = getThemeResource(context, R.attr.mediaRouteTheme);
+ if (style != 0) {
+ context = new ContextThemeWrapper(context, style);
+ }
+
+ return context;
+ }
+
+ /*
+ * The following two methods are to be used in conjunction. They should be used to prepare
+ * the context and theme for a super class constructor (the latter method relies on the
+ * former method to properly prepare the context):
+ * super(context = createThemedDialogContext(context, theme),
+ * createThemedDialogStyle(context));
*
- * @param context the parent context
- * @param style the resource ID of the style against which to inflate this context, or
- * {@code 0} to use the parent {@code context}'s default theme.
- * @return The themed context.
+ * It will apply theme in the following order (style lookups will be done in reverse):
+ * 1) Current theme
+ * 2) Supplied theme
+ * 3) Base Media Router theme
+ * 4) Custom Media Router theme, if provided
*/
- static Context createThemedContext(Context context, int style) {
- // First, apply dialog property overlay.
- Context themedContext =
- new ContextThemeWrapper(context, getStyledRouterThemeId(context, style));
- int customizedThemeId = getThemeResource(context, R.attr.mediaRouteTheme);
- return customizedThemeId == 0 ? themedContext
- : new ContextThemeWrapper(themedContext, customizedThemeId);
+ static Context createThemedDialogContext(Context context, int theme, boolean alertDialog) {
+ // 1) Current theme is already applied to the context
+
+ // 2) If no theme is supplied, look it up from the context (dialogTheme/alertDialogTheme)
+ if (theme == 0) {
+ theme = getThemeResource(context, !alertDialog
+ ? android.support.v7.appcompat.R.attr.dialogTheme
+ : android.support.v7.appcompat.R.attr.alertDialogTheme);
+ }
+ // Apply it
+ context = new ContextThemeWrapper(context, theme);
+
+ // 3) If a custom Media Router theme is provided then apply the base theme
+ if (getThemeResource(context, R.attr.mediaRouteTheme) != 0) {
+ context = new ContextThemeWrapper(context, getRouterThemeId(context));
+ }
+
+ return context;
}
+ // This method should be used in conjunction with the previous method.
+ static int createThemedDialogStyle(Context context) {
+ // 4) Apply the custom Media Router theme
+ int theme = getThemeResource(context, R.attr.mediaRouteTheme);
+ if (theme == 0) {
+ // 3) No custom MediaRouther theme was provided so apply the base theme instead
+ theme = getRouterThemeId(context);
+ }
- /**
- * Creates the theme resource ID intended to be used by dialogs.
- */
- static int createThemeForDialog(Context context, int style) {
- int customizedThemeId = getThemeResource(context, R.attr.mediaRouteTheme);
- return customizedThemeId != 0 ? customizedThemeId : getStyledRouterThemeId(context, style);
+ return theme;
}
+ // END. Previous two methods should be used in conjunction.
- public static int getThemeResource(Context context, int attr) {
+ static int getThemeResource(Context context, int attr) {
TypedValue value = new TypedValue();
return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0;
}
- public static float getDisabledAlpha(Context context) {
+ static float getDisabledAlpha(Context context) {
TypedValue value = new TypedValue();
return context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true)
? value.getFloat() : 0.5f;
}
- public static @ControllerColorType int getControllerColor(Context context, int style) {
+ static @ControllerColorType int getControllerColor(Context context, int style) {
int primaryColor = getThemeColor(context, style,
android.support.v7.appcompat.R.attr.colorPrimary);
if (ColorUtils.calculateContrast(COLOR_WHITE_ON_DARK_BACKGROUND, primaryColor)
@@ -92,7 +121,7 @@ final class MediaRouterThemeHelper {
return COLOR_DARK_ON_LIGHT_BACKGROUND;
}
- public static int getButtonTextColor(Context context) {
+ static int getButtonTextColor(Context context) {
int primaryColor = getThemeColor(context, 0,
android.support.v7.appcompat.R.attr.colorPrimary);
int backgroundColor = getThemeColor(context, 0, android.R.attr.colorBackground);
@@ -104,7 +133,7 @@ final class MediaRouterThemeHelper {
return primaryColor;
}
- public static void setMediaControlsBackgroundColor(
+ static void setMediaControlsBackgroundColor(
Context context, View mainControls, View groupControls, boolean hasGroup) {
int primaryColor = getThemeColor(context, 0,
android.support.v7.appcompat.R.attr.colorPrimary);
@@ -124,7 +153,7 @@ final class MediaRouterThemeHelper {
groupControls.setTag(primaryDarkColor);
}
- public static void setVolumeSliderColor(
+ static void setVolumeSliderColor(
Context context, MediaRouteVolumeSlider volumeSlider, View backgroundView) {
int controllerColor = getControllerColor(context, 0);
if (Color.alpha(controllerColor) != 0xFF) {
@@ -136,23 +165,10 @@ final class MediaRouterThemeHelper {
volumeSlider.setColor(controllerColor);
}
- // This is copied from {@link AlertDialog#resolveDialogTheme} to pre-evaluate theme in advance.
- public static int getAlertDialogResolvedTheme(Context context, int themeResId) {
- if (themeResId >= 0x01000000) { // start of real resource IDs.
- return themeResId;
- } else {
- TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(
- android.support.v7.appcompat.R.attr.alertDialogTheme, outValue, true);
- return outValue.resourceId;
- }
- }
-
private static boolean isLightTheme(Context context) {
TypedValue value = new TypedValue();
- return context.getTheme().resolveAttribute(
- android.support.v7.appcompat.R.attr.isLightTheme, value, true)
- && value.data != 0;
+ return context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.isLightTheme,
+ value, true) && value.data != 0;
}
private static int getThemeColor(Context context, int style, int attr) {
@@ -173,16 +189,16 @@ final class MediaRouterThemeHelper {
return value.data;
}
- private static int getStyledRouterThemeId(Context context, int style) {
+ private static int getRouterThemeId(Context context) {
int themeId;
if (isLightTheme(context)) {
- if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+ if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
themeId = R.style.Theme_MediaRouter_Light;
} else {
themeId = R.style.Theme_MediaRouter_Light_DarkControlPanel;
}
} else {
- if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+ if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
themeId = R.style.Theme_MediaRouter_LightControlPanel;
} else {
themeId = R.style.Theme_MediaRouter;
diff --git a/android/support/v7/util/DiffUtil.java b/android/support/v7/util/DiffUtil.java
index 6302666f..ebc33f31 100644
--- a/android/support/v7/util/DiffUtil.java
+++ b/android/support/v7/util/DiffUtil.java
@@ -16,6 +16,7 @@
package android.support.v7.util;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView;
@@ -348,6 +349,72 @@ public class DiffUtil {
}
/**
+ * Callback for calculating the diff between two non-null items in a list.
+ * <p>
+ * {@link Callback} serves two roles - list indexing, and item diffing. ItemCallback handles
+ * just the second of these, which allows separation of code that indexes into an array or List
+ * from the presentation-layer and content specific diffing code.
+ *
+ * @param <T> Type of items to compare.
+ */
+ public abstract static class ItemCallback<T> {
+ /**
+ * Called to check whether two objects represent the same item.
+ * <p>
+ * For example, if your items have unique ids, this method should check their id equality.
+ *
+ * @param oldItem The item in the old list.
+ * @param newItem The item in the new list.
+ * @return True if the two items represent the same object or false if they are different.
+ *
+ * @see Callback#areItemsTheSame(int, int)
+ */
+ public abstract boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem);
+
+ /**
+ * Called to check whether two items have the same data.
+ * <p>
+ * This information is used to detect if the contents of an item have changed.
+ * <p>
+ * This method to check equality instead of {@link Object#equals(Object)} so that you can
+ * change its behavior depending on your UI.
+ * <p>
+ * For example, if you are using DiffUtil with a
+ * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should
+ * return whether the items' visual representations are the same.
+ * <p>
+ * This method is called only if {@link #areItemsTheSame(T, T)} returns {@code true} for
+ * these items.
+ *
+ * @param oldItem The item in the old list.
+ * @param newItem The item in the new list.
+ * @return True if the contents of the items are the same or false if they are different.
+ *
+ * @see Callback#areContentsTheSame(int, int)
+ */
+ public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem);
+
+ /**
+ * When {@link #areItemsTheSame(T, T)} returns {@code true} for two items and
+ * {@link #areContentsTheSame(T, T)} returns false for them, this method is called to
+ * get a payload about the change.
+ * <p>
+ * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the
+ * particular field that changed in the item and your
+ * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
+ * information to run the correct animation.
+ * <p>
+ * Default implementation returns {@code null}.
+ *
+ * @see Callback#getChangePayload(int, int)
+ */
+ @SuppressWarnings({"WeakerAccess", "unused"})
+ public Object getChangePayload(@NonNull T oldItem, @NonNull T newItem) {
+ return null;
+ }
+ }
+
+ /**
* Snakes represent a match between two lists. It is optionally prefixed or postfixed with an
* add or remove operation. See the Myers' paper for details.
*/
diff --git a/android/support/v7/widget/AppCompatTextHelper.java b/android/support/v7/widget/AppCompatTextHelper.java
index 51510aa2..fa6196f5 100644
--- a/android/support/v7/widget/AppCompatTextHelper.java
+++ b/android/support/v7/widget/AppCompatTextHelper.java
@@ -29,6 +29,7 @@ import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
+import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.widget.TextViewCompat;
import android.support.v7.appcompat.R;
import android.text.method.PasswordTransformationMethod;
@@ -36,6 +37,8 @@ import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
+import java.lang.ref.WeakReference;
+
@RequiresApi(9)
class AppCompatTextHelper {
@@ -63,6 +66,7 @@ class AppCompatTextHelper {
private int mStyle = Typeface.NORMAL;
private Typeface mFontTypeface;
+ private boolean mAsyncFontPending;
AppCompatTextHelper(TextView view) {
mView = view;
@@ -213,8 +217,23 @@ class AppCompatTextHelper {
? R.styleable.TextAppearance_android_fontFamily
: R.styleable.TextAppearance_fontFamily;
if (!context.isRestricted()) {
+ final WeakReference<TextView> textViewWeak = new WeakReference<>(mView);
+ ResourcesCompat.FontCallback replyCallback = new ResourcesCompat.FontCallback() {
+ @Override
+ public void onFontRetrieved(@NonNull Typeface typeface) {
+ onAsyncTypefaceReceived(textViewWeak, typeface);
+ }
+
+ @Override
+ public void onFontRetrievalFailed(int reason) {
+ // Do nothing.
+ }
+ };
try {
- mFontTypeface = a.getFont(fontFamilyId, mStyle);
+ // Note the callback will be triggered on the UI thread.
+ mFontTypeface = a.getFont(fontFamilyId, mStyle, replyCallback);
+ // If this call gave us an immediate result, ignore any pending callbacks.
+ mAsyncFontPending = mFontTypeface == null;
} catch (UnsupportedOperationException | Resources.NotFoundException e) {
// Expected if it is not a font resource.
}
@@ -222,12 +241,16 @@ class AppCompatTextHelper {
if (mFontTypeface == null) {
// Try with String. This is done by TextView JB+, but fails in ICS
String fontFamilyName = a.getString(fontFamilyId);
- mFontTypeface = Typeface.create(fontFamilyName, mStyle);
+ if (fontFamilyName != null) {
+ mFontTypeface = Typeface.create(fontFamilyName, mStyle);
+ }
}
return;
}
if (a.hasValue(R.styleable.TextAppearance_android_typeface)) {
+ // Ignore previous pending fonts
+ mAsyncFontPending = false;
int typefaceIndex = a.getInt(R.styleable.TextAppearance_android_typeface, SANS);
switch (typefaceIndex) {
case SANS:
@@ -245,6 +268,16 @@ class AppCompatTextHelper {
}
}
+ private void onAsyncTypefaceReceived(WeakReference<TextView> textViewWeak, Typeface typeface) {
+ if (mAsyncFontPending) {
+ mFontTypeface = typeface;
+ final TextView textView = textViewWeak.get();
+ if (textView != null) {
+ textView.setTypeface(typeface, mStyle);
+ }
+ }
+ }
+
void onSetTextAppearance(Context context, int resId) {
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
resId, R.styleable.TextAppearance);
diff --git a/android/support/v7/widget/TintTypedArray.java b/android/support/v7/widget/TintTypedArray.java
index 22709551..384c4615 100644
--- a/android/support/v7/widget/TintTypedArray.java
+++ b/android/support/v7/widget/TintTypedArray.java
@@ -106,7 +106,8 @@ public class TintTypedArray {
* not a font resource.
*/
@Nullable
- public Typeface getFont(@StyleableRes int index, int style) {
+ public Typeface getFont(@StyleableRes int index, int style,
+ @Nullable ResourcesCompat.FontCallback fontCallback) {
final int resourceId = mWrapped.getResourceId(index, 0);
if (resourceId == 0) {
return null;
@@ -114,7 +115,7 @@ public class TintTypedArray {
if (mTypedValue == null) {
mTypedValue = new TypedValue();
}
- return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style);
+ return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style, fontCallback);
}
public int length() {
diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java
index 689ce954..de980b2f 100644
--- a/android/telephony/CarrierConfigManager.java
+++ b/android/telephony/CarrierConfigManager.java
@@ -763,6 +763,18 @@ public class CarrierConfigManager {
public static final String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
/**
+ * Some carriers will send call forwarding responses for voicemail in a format that is not 3gpp
+ * compliant, which causes issues during parsing. This causes the
+ * {@link com.android.internal.telephony.CallForwardInfo#number} to contain non-numerical
+ * characters instead of a number.
+ *
+ * If true, we will detect the non-numerical characters and replace them with "Voicemail".
+ * @hide
+ */
+ public static final String KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL =
+ "call_forwarding_map_non_number_to_voicemail_bool";
+
+ /**
* Determines whether conference calls are supported by a carrier. When {@code true},
* conference calling is supported, {@code false otherwise}.
*/
@@ -1573,6 +1585,25 @@ public class CarrierConfigManager {
public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL =
"show_ims_registration_status_bool";
+ /**
+ * The flag to disable the popup dialog which warns the user of data charges.
+ * @hide
+ */
+ public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL =
+ "disable_charge_indication_bool";
+
+ /**
+ * Boolean indicating whether to skip the call forwarding (CF) fail-to-disable dialog.
+ * The logic used to determine whether we succeeded in disabling is carrier specific,
+ * so the dialog may not always be accurate.
+ * {@code false} - show CF fail-to-disable dialog.
+ * {@code true} - skip showing CF fail-to-disable dialog.
+ *
+ * @hide
+ */
+ public static final String KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL =
+ "skip_cf_fail_to_disable_dialog_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1703,6 +1734,7 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_GSM_DTMF_TONE_DELAY_INT, 0);
sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0);
sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
+ sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false);
sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
@@ -1726,6 +1758,7 @@ public class CarrierConfigManager {
sDefaults.putString(KEY_CARRIER_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORT_DIRECT_FDN_DIALING_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_DEFAULT_DATA_ROAMING_ENABLED_BOOL, false);
+ sDefaults.putBoolean(KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL, false);
// MMS defaults
sDefaults.putBoolean(KEY_MMS_ALIAS_ENABLED_BOOL, false);
@@ -1840,6 +1873,7 @@ public class CarrierConfigManager {
sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
+ sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
}
/**
diff --git a/android/telephony/MbmsDownloadSession.java b/android/telephony/MbmsDownloadSession.java
index 764b7b22..9a9877a8 100644
--- a/android/telephony/MbmsDownloadSession.java
+++ b/android/telephony/MbmsDownloadSession.java
@@ -77,8 +77,9 @@ public class MbmsDownloadSession implements AutoCloseable {
* Integer extra that Android will attach to the intent supplied via
* {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
* Indicates the result code of the download. One of
- * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED}, or
- * {@link #RESULT_IO_ERROR}.
+ * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED},
+ * {@link #RESULT_IO_ERROR}, {@link #RESULT_DOWNLOAD_FAILURE}, {@link #RESULT_OUT_OF_STORAGE},
+ * {@link #RESULT_SERVICE_ID_NOT_DEFINED}, or {@link #RESULT_FILE_ROOT_UNREACHABLE}.
*
* This extra may also be used by the middleware when it is sending intents to the app.
*/
@@ -142,11 +143,41 @@ public class MbmsDownloadSession implements AutoCloseable {
/**
* Indicates that the download will not be completed due to an I/O error incurred while
- * writing to temp files. This commonly indicates that the device is out of storage space,
- * but may indicate other conditions as well (such as an SD card being removed).
+ * writing to temp files.
+ *
+ * This is likely a transient error and another {@link DownloadRequest} should be sent to try
+ * the download again.
*/
public static final int RESULT_IO_ERROR = 4;
- // TODO - more results!
+
+ /**
+ * Indicates that the Service ID specified in the {@link DownloadRequest} is incorrect due to
+ * the Id being incorrect, stale, expired, or similar.
+ */
+ public static final int RESULT_SERVICE_ID_NOT_DEFINED = 5;
+
+ /**
+ * Indicates that there was an error while processing downloaded files, such as a file repair or
+ * file decoding error and is not due to a file I/O error.
+ *
+ * This is likely a transient error and another {@link DownloadRequest} should be sent to try
+ * the download again.
+ */
+ public static final int RESULT_DOWNLOAD_FAILURE = 6;
+
+ /**
+ * Indicates that the file system is full and the {@link DownloadRequest} can not complete.
+ * Either space must be made on the current file system or the temp file root location must be
+ * changed to a location that is not full to download the temp files.
+ */
+ public static final int RESULT_OUT_OF_STORAGE = 7;
+
+ /**
+ * Indicates that the file root that was set is currently unreachable. This can happen if the
+ * temp files are set to be stored on external storage and the SD card was removed, for example.
+ * The temp file root should be changed before sending another DownloadRequest.
+ */
+ public static final int RESULT_FILE_ROOT_UNREACHABLE = 8;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
diff --git a/android/telephony/NetworkScanRequest.java b/android/telephony/NetworkScanRequest.java
index d2aef200..9674c930 100644
--- a/android/telephony/NetworkScanRequest.java
+++ b/android/telephony/NetworkScanRequest.java
@@ -19,6 +19,7 @@ package android.telephony;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.ArrayList;
import java.util.Arrays;
/**
@@ -38,6 +39,20 @@ public final class NetworkScanRequest implements Parcelable {
public static final int MAX_BANDS = 8;
/** @hide */
public static final int MAX_CHANNELS = 32;
+ /** @hide */
+ public static final int MAX_MCC_MNC_LIST_SIZE = 20;
+ /** @hide */
+ public static final int MIN_SEARCH_PERIODICITY_SEC = 5;
+ /** @hide */
+ public static final int MAX_SEARCH_PERIODICITY_SEC = 300;
+ /** @hide */
+ public static final int MIN_SEARCH_MAX_SEC = 60;
+ /** @hide */
+ public static final int MAX_SEARCH_MAX_SEC = 3600;
+ /** @hide */
+ public static final int MIN_INCREMENTAL_PERIODICITY_SEC = 1;
+ /** @hide */
+ public static final int MAX_INCREMENTAL_PERIODICITY_SEC = 10;
/** Performs the scan only once */
public static final int SCAN_TYPE_ONE_SHOT = 0;
@@ -46,24 +61,84 @@ public final class NetworkScanRequest implements Parcelable {
*
* The modem will start new scans periodically, and the interval between two scans is usually
* multiple minutes.
- * */
+ */
public static final int SCAN_TYPE_PERIODIC = 1;
/** Defines the type of the scan. */
public int scanType;
+ /**
+ * Search periodicity (in seconds).
+ * Expected range for the input is [5s - 300s]
+ * This value must be less than or equal to maxSearchTime
+ */
+ public int searchPeriodicity;
+
+ /**
+ * Maximum duration of the periodic search (in seconds).
+ * Expected range for the input is [60s - 3600s]
+ * If the search lasts this long, it will be terminated.
+ */
+ public int maxSearchTime;
+
+ /**
+ * Indicates whether the modem should report incremental
+ * results of the network scan to the client.
+ * FALSE – Incremental results are not reported.
+ * TRUE (default) – Incremental results are reported
+ */
+ public boolean incrementalResults;
+
+ /**
+ * Indicates the periodicity with which the modem should
+ * report incremental results to the client (in seconds).
+ * Expected range for the input is [1s - 10s]
+ * This value must be less than or equal to maxSearchTime
+ */
+ public int incrementalResultsPeriodicity;
+
/** Describes the radio access technologies with bands or channels that need to be scanned. */
public RadioAccessSpecifier[] specifiers;
/**
+ * Describes the List of PLMN ids (MCC-MNC)
+ * If any PLMN of this list is found, search should end at that point and
+ * results with all PLMN found till that point should be sent as response.
+ * If list not sent, search to be completed till end and all PLMNs found to be reported.
+ * Max size of array is MAX_MCC_MNC_LIST_SIZE
+ */
+ public ArrayList<String> mccMncs;
+
+ /**
* Creates a new NetworkScanRequest with scanType and network specifiers
*
* @param scanType The type of the scan
* @param specifiers the radio network with bands / channels to be scanned
+ * @param searchPeriodicity Search periodicity (in seconds)
+ * @param maxSearchTime Maximum duration of the periodic search (in seconds)
+ * @param incrementalResults Indicates whether the modem should report incremental
+ * results of the network scan to the client
+ * @param incrementalResultsPeriodicity Indicates the periodicity with which the modem should
+ * report incremental results to the client (in seconds)
+ * @param mccMncs Describes the List of PLMN ids (MCC-MNC)
*/
- public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers) {
+ public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers,
+ int searchPeriodicity,
+ int maxSearchTime,
+ boolean incrementalResults,
+ int incrementalResultsPeriodicity,
+ ArrayList<String> mccMncs) {
this.scanType = scanType;
this.specifiers = specifiers;
+ this.searchPeriodicity = searchPeriodicity;
+ this.maxSearchTime = maxSearchTime;
+ this.incrementalResults = incrementalResults;
+ this.incrementalResultsPeriodicity = incrementalResultsPeriodicity;
+ if (mccMncs != null) {
+ this.mccMncs = mccMncs;
+ } else {
+ this.mccMncs = new ArrayList<>();
+ }
}
@Override
@@ -75,6 +150,11 @@ public final class NetworkScanRequest implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(scanType);
dest.writeParcelableArray(specifiers, flags);
+ dest.writeInt(searchPeriodicity);
+ dest.writeInt(maxSearchTime);
+ dest.writeBoolean(incrementalResults);
+ dest.writeInt(incrementalResultsPeriodicity);
+ dest.writeStringList(mccMncs);
}
private NetworkScanRequest(Parcel in) {
@@ -82,6 +162,12 @@ public final class NetworkScanRequest implements Parcelable {
specifiers = (RadioAccessSpecifier[]) in.readParcelableArray(
Object.class.getClassLoader(),
RadioAccessSpecifier.class);
+ searchPeriodicity = in.readInt();
+ maxSearchTime = in.readInt();
+ incrementalResults = in.readBoolean();
+ incrementalResultsPeriodicity = in.readInt();
+ mccMncs = new ArrayList<>();
+ in.readStringList(mccMncs);
}
@Override
@@ -99,13 +185,24 @@ public final class NetworkScanRequest implements Parcelable {
}
return (scanType == nsr.scanType
- && Arrays.equals(specifiers, nsr.specifiers));
+ && Arrays.equals(specifiers, nsr.specifiers)
+ && searchPeriodicity == nsr.searchPeriodicity
+ && maxSearchTime == nsr.maxSearchTime
+ && incrementalResults == nsr.incrementalResults
+ && incrementalResultsPeriodicity == nsr.incrementalResultsPeriodicity
+ && (((mccMncs != null)
+ && mccMncs.equals(nsr.mccMncs))));
}
@Override
public int hashCode () {
return ((scanType * 31)
- + (Arrays.hashCode(specifiers)) * 37);
+ + (Arrays.hashCode(specifiers)) * 37
+ + (searchPeriodicity * 41)
+ + (maxSearchTime * 43)
+ + ((incrementalResults == true? 1 : 0) * 47)
+ + (incrementalResultsPeriodicity * 53)
+ + (mccMncs.hashCode() * 59));
}
public static final Creator<NetworkScanRequest> CREATOR =
diff --git a/android/telephony/ServiceState.java b/android/telephony/ServiceState.java
index e448fb2a..116e711e 100644
--- a/android/telephony/ServiceState.java
+++ b/android/telephony/ServiceState.java
@@ -1197,15 +1197,6 @@ public class ServiceState implements Parcelable {
}
}
- /**
- * @Deprecated to be removed Q3 2013 use {@link #getVoiceNetworkType}
- * @hide
- */
- public int getNetworkType() {
- Rlog.e(LOG_TAG, "ServiceState.getNetworkType() DEPRECATED will be removed *******");
- return rilRadioTechnologyToNetworkType(mRilVoiceRadioTechnology);
- }
-
/** @hide */
public int getDataNetworkType() {
return rilRadioTechnologyToNetworkType(mRilDataRadioTechnology);
diff --git a/android/telephony/mbms/DownloadRequest.java b/android/telephony/mbms/DownloadRequest.java
index 5a57f322..f0d60b68 100644
--- a/android/telephony/mbms/DownloadRequest.java
+++ b/android/telephony/mbms/DownloadRequest.java
@@ -16,6 +16,7 @@
package android.telephony.mbms;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Intent;
import android.net.Uri;
@@ -26,7 +27,6 @@ import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
@@ -71,6 +71,19 @@ public final class DownloadRequest implements Parcelable {
private String appIntent;
private int version = CURRENT_VERSION;
+
+ /**
+ * Builds a new DownloadRequest.
+ * @param sourceUri the source URI for the DownloadRequest to be built. This URI should
+ * never be null.
+ */
+ public Builder(@NonNull Uri sourceUri) {
+ if (sourceUri == null) {
+ throw new IllegalArgumentException("Source URI must be non-null.");
+ }
+ source = sourceUri;
+ }
+
/**
* Sets the service from which the download request to be built will download from.
* @param serviceInfo
@@ -92,15 +105,6 @@ public final class DownloadRequest implements Parcelable {
}
/**
- * Sets the source URI for the download request to be built.
- * @param source
- */
- public Builder setSource(Uri source) {
- this.source = source;
- return this;
- }
-
- /**
* Set the subscription ID on which the file(s) should be downloaded.
* @param subscriptionId
*/
@@ -316,9 +320,11 @@ public final class DownloadRequest implements Parcelable {
throw new RuntimeException("Could not get sha256 hash object");
}
if (version >= 1) {
- // Hash the source URI, destination URI, and the app intent
+ // Hash the source URI and the app intent
digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
- digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
+ if (serializedResultIntentForApp != null) {
+ digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
+ }
}
// Add updates for future versions here
return Base64.encodeToString(digest.digest(), Base64.URL_SAFE | Base64.NO_WRAP);
diff --git a/android/telephony/mbms/MbmsDownloadReceiver.java b/android/telephony/mbms/MbmsDownloadReceiver.java
index fe275372..9af1eb9e 100644
--- a/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -287,7 +287,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
return;
}
- List<Uri> tempFiles = intent.getParcelableExtra(VendorUtils.EXTRA_TEMP_LIST);
+ List<Uri> tempFiles = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_TEMP_LIST);
if (tempFiles == null) {
return;
}
@@ -309,7 +309,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
return;
}
int fdCount = intent.getIntExtra(VendorUtils.EXTRA_FD_COUNT, 0);
- List<Uri> pausedList = intent.getParcelableExtra(VendorUtils.EXTRA_PAUSED_LIST);
+ List<Uri> pausedList = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_PAUSED_LIST);
if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
Log.i(LOG_TAG, "No temp files actually requested. Ending.");
@@ -492,9 +492,14 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException("Package manager couldn't find " + context.getPackageName());
}
+ if (appInfo.metaData == null) {
+ throw new RuntimeException("App must declare the file provider authority as metadata " +
+ "in the manifest.");
+ }
String authority = appInfo.metaData.getString(MBMS_FILE_PROVIDER_META_DATA_KEY);
if (authority == null) {
- throw new RuntimeException("Must declare the file provider authority as meta data");
+ throw new RuntimeException("App must declare the file provider authority as metadata " +
+ "in the manifest.");
}
return authority;
}
diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index 2f85a1df..c3b2c482 100644
--- a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -113,6 +113,10 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
@Override
public final int initialize(final int subscriptionId,
final IMbmsDownloadSessionCallback callback) throws RemoteException {
+ if (callback == null) {
+ throw new NullPointerException("Callback must not be null");
+ }
+
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
@@ -240,6 +244,13 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
public final int registerStateCallback(final DownloadRequest downloadRequest,
final IDownloadStateCallback callback, int flags) throws RemoteException {
final int uid = Binder.getCallingUid();
+ if (downloadRequest == null) {
+ throw new NullPointerException("Download request must not be null");
+ }
+ if (callback == null) {
+ throw new NullPointerException("Callback must not be null");
+ }
+
DeathRecipient deathRecipient = new DeathRecipient() {
@Override
public void binderDied() {
@@ -292,6 +303,13 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
public final int unregisterStateCallback(
final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
throws RemoteException {
+ if (downloadRequest == null) {
+ throw new NullPointerException("Download request must not be null");
+ }
+ if (callback == null) {
+ throw new NullPointerException("Callback must not be null");
+ }
+
DeathRecipient deathRecipient =
mDownloadCallbackDeathRecipients.remove(callback.asBinder());
if (deathRecipient == null) {
diff --git a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index f8f370a5..65b726df 100644
--- a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -65,6 +65,10 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
@Override
public final int initialize(final IMbmsStreamingSessionCallback callback,
final int subscriptionId) throws RemoteException {
+ if (callback == null) {
+ throw new NullPointerException("Callback must not be null");
+ }
+
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
@@ -152,6 +156,10 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
@Override
public int startStreaming(final int subscriptionId, String serviceId,
final IStreamingServiceCallback callback) throws RemoteException {
+ if (callback == null) {
+ throw new NullPointerException("Callback must not be null");
+ }
+
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
diff --git a/android/text/BoringLayoutCreateDrawPerfTest.java b/android/text/BoringLayoutCreateDrawPerfTest.java
index 47dd257b..586c3852 100644
--- a/android/text/BoringLayoutCreateDrawPerfTest.java
+++ b/android/text/BoringLayoutCreateDrawPerfTest.java
@@ -46,7 +46,7 @@ public class BoringLayoutCreateDrawPerfTest {
private static final float SPACING_ADD = 10f;
private static final float SPACING_MULT = 1.5f;
- @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+ @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/android/text/BoringLayoutIsBoringPerfTest.java b/android/text/BoringLayoutIsBoringPerfTest.java
index 34de65de..9d11f295 100644
--- a/android/text/BoringLayoutIsBoringPerfTest.java
+++ b/android/text/BoringLayoutIsBoringPerfTest.java
@@ -40,7 +40,7 @@ public class BoringLayoutIsBoringPerfTest {
private static final boolean[] BOOLEANS = new boolean[]{false, true};
- @Parameterized.Parameters(name = "cached={4},{1} chars,{0}")
+ @Parameterized.Parameters(name = "cached={4},{1}chars,{0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/android/text/DynamicLayout.java b/android/text/DynamicLayout.java
index 24260c4f..fba358cf 100644
--- a/android/text/DynamicLayout.java
+++ b/android/text/DynamicLayout.java
@@ -299,7 +299,7 @@ public class DynamicLayout extends Layout
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
- private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
+ private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
}
/**
@@ -440,7 +440,7 @@ public class DynamicLayout extends Layout
mEllipsizeAt = null;
}
- mObjects = new PackedObjectVector<Directions>(1);
+ mObjects = new PackedObjectVector<>(1);
// Initial state is a single line with 0 characters (0 to 0), with top at 0 and bottom at
// whatever is natural, and undefined ellipsis.
@@ -1050,7 +1050,7 @@ public class DynamicLayout extends Layout
private static class ChangeWatcher implements TextWatcher, SpanWatcher {
public ChangeWatcher(DynamicLayout layout) {
- mLayout = new WeakReference<DynamicLayout>(layout);
+ mLayout = new WeakReference<>(layout);
}
private void reflow(CharSequence s, int where, int before, int after) {
diff --git a/android/text/Hyphenator.java b/android/text/Hyphenator.java
index ad26f23a..4f1488e1 100644
--- a/android/text/Hyphenator.java
+++ b/android/text/Hyphenator.java
@@ -16,258 +16,15 @@
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;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.HashMap;
-import java.util.Locale;
-
/**
- * Hyphenator is a wrapper class for a native implementation of automatic hyphenation,
+ * Hyphenator just initializes the native implementation of automatic hyphenation,
* in essence finding valid hyphenation opportunities in a word.
*
* @hide
*/
public class Hyphenator {
- private static String TAG = "Hyphenator";
-
- private final static Object sLock = new Object();
-
- @GuardedBy("sLock")
- final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();
-
- private final long mNativePtr;
- private final HyphenationData mData;
-
- private Hyphenator(long nativePtr, HyphenationData data) {
- mNativePtr = nativePtr;
- mData = data;
- }
-
- public long getNativePtr() {
- return mNativePtr;
- }
-
- public static Hyphenator get(@Nullable Locale locale) {
- synchronized (sLock) {
- Hyphenator result = sMap.get(locale);
- if (result != null) {
- return result;
- }
-
- // If there's a variant, fall back to language+variant only, if available
- final String variant = locale.getVariant();
- if (!variant.isEmpty()) {
- final Locale languageAndVariantOnlyLocale =
- new Locale(locale.getLanguage(), "", variant);
- result = sMap.get(languageAndVariantOnlyLocale);
- if (result != null) {
- return putAlias(locale, result);
- }
- }
-
- // Fall back to language-only, if available
- final Locale languageOnlyLocale = new Locale(locale.getLanguage());
- result = sMap.get(languageOnlyLocale);
- if (result != null) {
- return putAlias(locale, result);
- }
-
- // Fall back to script-only, if available
- final String script = locale.getScript();
- if (!script.equals("")) {
- final Locale scriptOnlyLocale = new Locale.Builder()
- .setLanguage("und")
- .setScript(script)
- .build();
- result = sMap.get(scriptOnlyLocale);
- if (result != null) {
- return putAlias(locale, result);
- }
- }
-
- return putEmptyAlias(locale);
- }
- }
-
- private static class HyphenationData {
- 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);
-
- // Create empty HyphenationData.
- private HyphenationData(int minPrefix, int minSuffix) {
- mMinPrefix = minPrefix;
- mMinSuffix = minSuffix;
- mDataAddress = 0;
- }
-
- 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;
- }
- }
- }
-
- // 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;
- }
-
- // 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);
- }
-
- // 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
- * for all locales loaded and ready to use so we don't have to do any file IO
- * on the UI thread when drawing text in different locales.
- *
- * @hide
- */
public static void init() {
- 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
- }
- };
-
- private static native long nBuildHyphenator(/* non-zero */ long dataAddress,
- @NonNull String langTag, @IntRange(from = 1) int minPrefix,
- @IntRange(from = 1) int minSuffix);
+ nInit();
+ }
+ private static native void nInit();
}
diff --git a/android/text/Layout.java b/android/text/Layout.java
index 60fff738..ac5c2e92 100644
--- a/android/text/Layout.java
+++ b/android/text/Layout.java
@@ -319,8 +319,6 @@ public abstract class Layout {
private float getJustifyWidth(int lineNum) {
Alignment paraAlign = mAlignment;
- TabStops tabStops = null;
- boolean tabStopsIsInitialized = false;
int left = 0;
int right = mWidth;
@@ -371,10 +369,6 @@ public abstract class Layout {
}
}
- if (getLineContainsTab(lineNum)) {
- tabStops = new TabStops(TAB_INCREMENT, spans);
- }
-
final Alignment align;
if (paraAlign == Alignment.ALIGN_LEFT) {
align = (dir == DIR_LEFT_TO_RIGHT) ? Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
@@ -1423,7 +1417,6 @@ public abstract class Layout {
float dist = Math.abs(getHorizontal(max, primary) - horiz);
if (dist <= bestdist) {
- bestdist = dist;
best = max;
}
@@ -1570,7 +1563,7 @@ public abstract class Layout {
// XXX: we don't care about tabs
tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null);
caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
- tl = TextLine.recycle(tl);
+ TextLine.recycle(tl);
return caret;
}
@@ -1894,10 +1887,7 @@ public abstract class Layout {
int margin = 0;
- boolean isFirstParaLine = lineStart == 0 ||
- spanned.charAt(lineStart - 1) == '\n';
-
- boolean useFirstLineMargin = isFirstParaLine;
+ boolean useFirstLineMargin = lineStart == 0 || spanned.charAt(lineStart - 1) == '\n';
for (int i = 0; i < spans.length; i++) {
if (spans[i] instanceof LeadingMarginSpan2) {
int spStart = spanned.getSpanStart(spans[i]);
diff --git a/android/text/PaintMeasureDrawPerfTest.java b/android/text/PaintMeasureDrawPerfTest.java
index 00b60add..67687985 100644
--- a/android/text/PaintMeasureDrawPerfTest.java
+++ b/android/text/PaintMeasureDrawPerfTest.java
@@ -42,7 +42,7 @@ public class PaintMeasureDrawPerfTest {
private static final boolean[] BOOLEANS = new boolean[]{false, true};
- @Parameterized.Parameters(name = "cached={1},{0} chars")
+ @Parameterized.Parameters(name = "cached={1},{0}chars")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/android/text/StaticLayout.java b/android/text/StaticLayout.java
index 961cd8ee..5c60188d 100644
--- a/android/text/StaticLayout.java
+++ b/android/text/StaticLayout.java
@@ -21,21 +21,18 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Paint;
-import android.os.LocaleList;
import android.text.style.LeadingMarginSpan;
import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
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.util.Arrays;
-import java.util.Locale;
/**
* StaticLayout is a Layout for text that will not be edited after it
@@ -101,7 +98,6 @@ 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;
@@ -118,7 +114,6 @@ 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);
@@ -409,17 +404,6 @@ public class StaticLayout extends Layout {
return this;
}
- @NonNull
- private long[] getHyphenators(@NonNull LocaleList locales) {
- final int length = locales.size();
- final long[] result = new long[length];
- for (int i = 0; i < length; i++) {
- final Locale locale = locales.get(i);
- result[i] = Hyphenator.get(locale).getNativePtr();
- }
- return result;
- }
-
/**
* Measurement and break iteration is done in native code. The protocol for using
* the native code is as follows.
@@ -433,35 +417,17 @@ public class StaticLayout extends Layout {
* + addStyleRun (a text run, to be measured in native code)
* + addReplacementRun (a replacement run, width is given)
*
- * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
* Run nComputeLineBreaks() to obtain line breaks for the paragraph.
*
* After all paragraphs, call finish() to release expensive buffers.
*/
- private Pair<String, long[]> getLocaleAndHyphenatorIfChanged(TextPaint paint) {
- final LocaleList locales = paint.getTextLocales();
- final String languageTags;
- long[] hyphenators;
- if (!locales.equals(mLocales)) {
- 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 */ 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);
+ nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
}
/* 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);
+ nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
}
/**
@@ -519,9 +485,7 @@ public class StaticLayout extends Layout {
// This will go away and be subsumed by native builder code
private MeasuredText mMeasuredText;
- private LocaleList mLocales;
-
- private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
+ private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
}
public StaticLayout(CharSequence source, TextPaint paint,
@@ -810,9 +774,6 @@ 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,
@@ -866,10 +827,9 @@ public class StaticLayout extends Layout {
spanEndCacheCount++;
}
- nGetWidths(b.mNativePtr, widths);
int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
- lineBreaks.breaks.length);
+ lineBreaks.breaks.length, widths);
final int[] breaks = lineBreaks.breaks;
final float[] lineWidths = lineBreaks.widths;
@@ -947,10 +907,10 @@ public class StaticLayout extends Layout {
boolean moreChars = (endPos < bufEnd);
final int ascent = fallbackLineSpacing
- ? Math.min(fmAscent, (int) Math.round(ascents[breakIndex]))
+ ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
: fmAscent;
final int descent = fallbackLineSpacing
- ? Math.max(fmDescent, (int) Math.round(descents[breakIndex]))
+ ? Math.max(fmDescent, Math.round(descents[breakIndex]))
: fmDescent;
v = out(source, here, endPos,
ascent, descent, fmTop, fmBottom,
@@ -1177,7 +1137,7 @@ public class StaticLayout extends Layout {
mWorkPaint.set(paint);
do {
final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
- widthStart, tempAvail, where, line, textWidth, mWorkPaint, forceEllipsis, dir);
+ widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir);
if (ellipsizedWidth <= avail) {
lineFits = true;
} else {
@@ -1207,7 +1167,7 @@ public class StaticLayout extends Layout {
// This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
// should not be accessed while the method is running.
private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
- int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
+ int widthStart, float avail, TextUtils.TruncateAt where, int line,
TextPaint paint, boolean forceEllipsis, int dir) {
final int savedHyphenEdit = paint.getHyphenEdit();
paint.setHyphenEdit(0);
@@ -1541,26 +1501,28 @@ public class StaticLayout extends Layout {
@Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
@IntRange(from = 0) int indentsOffset);
+ // TODO: Make this method CriticalNative once native code defers doing layouts.
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);
+ @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);
- private static native void nAddReplacementRun(/* non-zero */ long nativePtr,
+ // TODO: Make this method CriticalNative once native code defers doing layouts.
+ private static native void nAddReplacementRun(
+ /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
@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);
+ @FloatRange(from = 0.0f) float width);
// populates LineBreaks and returns the number of breaks found
//
// the arrays inside the LineBreaks objects are passed in as well
// to reduce the number of JNI calls in the common case where the
// arrays do not have to be resized
+ // The individual character widths will be returned in charWidths. The length of charWidths must
+ // be at least the length of the text.
private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
- float[] recycleDescents, int[] recycleFlags, int recycleLength);
+ float[] recycleDescents, int[] recycleFlags, int recycleLength,
+ float[] charWidths);
private int mLineCount;
private int mTopPadding, mBottomPadding;
diff --git a/android/text/StaticLayoutCreateDrawPerfTest.java b/android/text/StaticLayoutCreateDrawPerfTest.java
index 356e2e0d..bfdb7589 100644
--- a/android/text/StaticLayoutCreateDrawPerfTest.java
+++ b/android/text/StaticLayoutCreateDrawPerfTest.java
@@ -50,7 +50,7 @@ public class StaticLayoutCreateDrawPerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+ @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/android/text/StaticLayout_Delegate.java b/android/text/StaticLayout_Delegate.java
index 63337f08..def3c91c 100644
--- a/android/text/StaticLayout_Delegate.java
+++ b/android/text/StaticLayout_Delegate.java
@@ -13,7 +13,6 @@ import android.icu.util.ULocale;
import android.text.Primitive.PrimitiveType;
import android.text.StaticLayout.LineBreaks;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -72,13 +71,11 @@ public class StaticLayout_Delegate {
@LayoutlibDelegate
/*package*/ static void nAddStyleRun(long nativeBuilder, long nativePaint, int start,
- int end, boolean isRtl, String languageTags, long[] hyphenators) {
+ int end, boolean isRtl) {
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;
measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
@@ -86,30 +83,20 @@ public class StaticLayout_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width,
- String languageTags, long[] hyphenators) {
+ /*package*/ static void nAddReplacementRun(long nativeBuilder, long nativePaint, int start,
+ int end, float width) {
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);
}
@LayoutlibDelegate
- /*package*/ static void nGetWidths(long nativeBuilder, float[] floatsArray) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder != null) {
- System.arraycopy(builder.mWidths, 0, floatsArray, 0, builder.mWidths.length);
- }
- }
-
- @LayoutlibDelegate
/*package*/ static int nComputeLineBreaks(long nativeBuilder, LineBreaks recycle,
int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
- float[] recycleDescents, int[] recycleFlags, int recycleLength) {
+ float[] recycleDescents, int[] recycleFlags, int recycleLength, float[] charWidths) {
Builder builder = sBuilderManager.getDelegate(nativeBuilder);
if (builder == null) {
@@ -118,7 +105,7 @@ public class StaticLayout_Delegate {
// compute all possible breakpoints.
int length = builder.mWidths.length;
- BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocales));
+ BreakIterator it = BreakIterator.getLineInstance();
it.setText(new Segment(builder.mText, 0, length));
// average word length in english is 5. So, initialize the possible breaks with a guess.
@@ -149,6 +136,7 @@ public class StaticLayout_Delegate {
builder.mTabStopCalculator);
}
builder.mLineBreaker.computeBreaks(recycle);
+ System.arraycopy(builder.mWidths, 0, charWidths, 0, builder.mWidths.length);
return recycle.breaks.length;
}
@@ -206,11 +194,9 @@ public class StaticLayout_Delegate {
* Java representation of the native Builder class.
*/
private static class Builder {
- String mLocales;
char[] mText;
float[] mWidths;
LineBreaker mLineBreaker;
- long[] mNativeHyphenators;
int mBreakStrategy;
LineWidth mLineWidth;
TabStops mTabStopCalculator;
diff --git a/android/text/TextLine.java b/android/text/TextLine.java
index 2dbff100..20c0ed87 100644
--- a/android/text/TextLine.java
+++ b/android/text/TextLine.java
@@ -73,7 +73,7 @@ class TextLine {
new SpanSet<ReplacementSpan>(ReplacementSpan.class);
private final DecorationInfo mDecorationInfo = new DecorationInfo();
- private final ArrayList<DecorationInfo> mDecorations = new ArrayList();
+ private final ArrayList<DecorationInfo> mDecorations = new ArrayList<>();
private static final TextLine[] sCached = new TextLine[3];
@@ -340,14 +340,14 @@ class TextLine {
boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
if (inSegment && advance) {
- return h += measureRun(segstart, offset, j, runIsRtl, fmi);
+ return h + measureRun(segstart, offset, j, runIsRtl, fmi);
}
float w = measureRun(segstart, j, j, runIsRtl, fmi);
h += advance ? w : -w;
if (inSegment) {
- return h += measureRun(segstart, offset, j, runIsRtl, null);
+ return h + measureRun(segstart, offset, j, runIsRtl, null);
}
if (codept == '\t') {
@@ -828,14 +828,14 @@ class TextLine {
}
if (info.isUnderlineText) {
final float thickness =
- Math.max(((Paint) wp).getUnderlineThickness(), 1.0f);
+ Math.max(wp.getUnderlineThickness(), 1.0f);
drawStroke(wp, c, wp.getColor(), wp.getUnderlinePosition(), thickness,
decorationXLeft, decorationXRight, y);
}
if (info.isStrikeThruText) {
final float thickness =
- Math.max(((Paint) wp).getStrikeThruThickness(), 1.0f);
+ Math.max(wp.getStrikeThruThickness(), 1.0f);
drawStroke(wp, c, wp.getColor(), wp.getStrikeThruPosition(), thickness,
decorationXLeft, decorationXRight, y);
}
diff --git a/android/text/TextViewSetTextMeasurePerfTest.java b/android/text/TextViewSetTextMeasurePerfTest.java
index a2bf33e1..ff2d57ed 100644
--- a/android/text/TextViewSetTextMeasurePerfTest.java
+++ b/android/text/TextViewSetTextMeasurePerfTest.java
@@ -40,7 +40,7 @@ import java.util.Locale;
import java.util.Random;
/**
- * Performance test for multi line, single style {@link StaticLayout} creation/draw.
+ * Performance test for {@link TextView} measure/draw.
*/
@LargeTest
@RunWith(Parameterized.class)
@@ -51,7 +51,7 @@ public class TextViewSetTextMeasurePerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+ @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/android/util/Log.java b/android/util/Log.java
index 02998653..b94e48b3 100644
--- a/android/util/Log.java
+++ b/android/util/Log.java
@@ -16,45 +16,12 @@
package android.util;
-import android.os.DeadSystemException;
-
-import com.android.internal.os.RuntimeInit;
-import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.LineBreakBufferedWriter;
-
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.io.Writer;
import java.net.UnknownHostException;
/**
- * API for sending log output.
- *
- * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()},
- * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs.
- * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>.
- *
- * <p>The order in terms of verbosity, from least to most is
- * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled
- * into an application except during development. Debug logs are compiled
- * in but stripped at runtime. Error, warning and info logs are always kept.
- *
- * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant
- * in your class:
- *
- * <pre>private static final String TAG = "MyActivity";</pre>
- *
- * and use that in subsequent calls to the log methods.
- * </p>
- *
- * <p><b>Tip:</b> Don't forget that when you make a call like
- * <pre>Log.v(TAG, "index=" + i);</pre>
- * that when you're building the string to pass into Log.d, the compiler uses a
- * StringBuilder and at least three allocations occur: the StringBuilder
- * itself, the buffer, and the String object. Realistically, there is also
- * another buffer allocation and copy, and even more pressure on the gc.
- * That means that if your log message is filtered out, you might be doing
- * significant work and incurring significant overhead.
+ * Mock Log implementation for testing on non android host.
*/
public final class Log {
@@ -88,29 +55,6 @@ public final class Log {
*/
public static final int ASSERT = 7;
- /**
- * Exception class used to capture a stack trace in {@link #wtf}.
- * @hide
- */
- public static class TerribleFailure extends Exception {
- TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
- }
-
- /**
- * Interface to handle terrible failures from {@link #wtf}.
- *
- * @hide
- */
- public interface TerribleFailureHandler {
- void onTerribleFailure(String tag, TerribleFailure what, boolean system);
- }
-
- private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() {
- public void onTerribleFailure(String tag, TerribleFailure what, boolean system) {
- RuntimeInit.wtf(tag, what, system);
- }
- };
-
private Log() {
}
@@ -121,7 +65,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int v(String tag, String msg) {
- return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
+ return println(LOG_ID_MAIN, VERBOSE, tag, msg);
}
/**
@@ -132,7 +76,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int v(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
+ return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -142,7 +86,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int d(String tag, String msg) {
- return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
+ return println(LOG_ID_MAIN, DEBUG, tag, msg);
}
/**
@@ -153,7 +97,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int d(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
+ return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -163,7 +107,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int i(String tag, String msg) {
- return println_native(LOG_ID_MAIN, INFO, tag, msg);
+ return println(LOG_ID_MAIN, INFO, tag, msg);
}
/**
@@ -174,7 +118,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int i(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
+ return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -184,7 +128,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int w(String tag, String msg) {
- return println_native(LOG_ID_MAIN, WARN, tag, msg);
+ return println(LOG_ID_MAIN, WARN, tag, msg);
}
/**
@@ -195,31 +139,9 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, WARN, tag, msg, tr);
+ return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
}
- /**
- * Checks to see whether or not a log for the specified tag is loggable at the specified level.
- *
- * The default level of any tag is set to INFO. This means that any level above and including
- * INFO will be logged. Before you make any calls to a logging method you should check to see
- * if your tag should be logged. You can change the default level by setting a system property:
- * 'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
- * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
- * turn off all logging for your tag. You can also create a local.prop file that with the
- * following in it:
- * 'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
- * and place that in /data/local.prop.
- *
- * @param tag The tag to check.
- * @param level The level to check.
- * @return Whether or not that this is allowed to be logged.
- * @throws IllegalArgumentException is thrown if the tag.length() > 23
- * for Nougat (7.0) releases (API <= 23) and prior, there is no
- * tag limit of concern after this API level.
- */
- public static native boolean isLoggable(String tag, int level);
-
/*
* Send a {@link #WARN} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
@@ -227,7 +149,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, Throwable tr) {
- return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
+ return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
}
/**
@@ -237,7 +159,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int e(String tag, String msg) {
- return println_native(LOG_ID_MAIN, ERROR, tag, msg);
+ return println(LOG_ID_MAIN, ERROR, tag, msg);
}
/**
@@ -248,82 +170,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int e(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
- }
-
- /**
- * What a Terrible Failure: Report a condition that should never happen.
- * The error will always be logged at level ASSERT with the call stack.
- * Depending on system configuration, a report may be added to the
- * {@link android.os.DropBoxManager} and/or the process may be terminated
- * immediately with an error dialog.
- * @param tag Used to identify the source of a log message.
- * @param msg The message you would like logged.
- */
- public static int wtf(String tag, String msg) {
- return wtf(LOG_ID_MAIN, tag, msg, null, false, false);
- }
-
- /**
- * Like {@link #wtf(String, String)}, but also writes to the log the full
- * call stack.
- * @hide
- */
- public static int wtfStack(String tag, String msg) {
- return wtf(LOG_ID_MAIN, tag, msg, null, true, false);
- }
-
- /**
- * What a Terrible Failure: Report an exception that should never happen.
- * Similar to {@link #wtf(String, String)}, with an exception to log.
- * @param tag Used to identify the source of a log message.
- * @param tr An exception to log.
- */
- public static int wtf(String tag, Throwable tr) {
- return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false);
- }
-
- /**
- * What a Terrible Failure: Report an exception that should never happen.
- * Similar to {@link #wtf(String, Throwable)}, with a message as well.
- * @param tag Used to identify the source of a log message.
- * @param msg The message you would like logged.
- * @param tr An exception to log. May be null.
- */
- public static int wtf(String tag, String msg, Throwable tr) {
- return wtf(LOG_ID_MAIN, tag, msg, tr, false, false);
- }
-
- static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
- boolean system) {
- TerribleFailure what = new TerribleFailure(msg, tr);
- // Only mark this as ERROR, do not use ASSERT since that should be
- // reserved for cases where the system is guaranteed to abort.
- // The onTerribleFailure call does not always cause a crash.
- int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
- sWtfHandler.onTerribleFailure(tag, what, system);
- return bytes;
- }
-
- static void wtfQuiet(int logId, String tag, String msg, boolean system) {
- TerribleFailure what = new TerribleFailure(msg, null);
- sWtfHandler.onTerribleFailure(tag, what, system);
- }
-
- /**
- * Sets the terrible failure handler, for testing.
- *
- * @return the old handler
- *
- * @hide
- */
- public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) {
- if (handler == null) {
- throw new NullPointerException("handler == null");
- }
- TerribleFailureHandler oldHandler = sWtfHandler;
- sWtfHandler = handler;
- return oldHandler;
+ return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -346,7 +193,7 @@ public final class Log {
}
StringWriter sw = new StringWriter();
- PrintWriter pw = new FastPrintWriter(sw, false, 256);
+ PrintWriter pw = new PrintWriter(sw);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
@@ -361,7 +208,7 @@ public final class Log {
* @return The number of bytes written.
*/
public static int println(int priority, String tag, String msg) {
- return println_native(LOG_ID_MAIN, priority, tag, msg);
+ return println(LOG_ID_MAIN, priority, tag, msg);
}
/** @hide */ public static final int LOG_ID_MAIN = 0;
@@ -370,115 +217,9 @@ public final class Log {
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
/** @hide */ public static final int LOG_ID_CRASH = 4;
- /** @hide */ public static native int println_native(int bufID,
- int priority, String tag, String msg);
-
- /**
- * Return the maximum payload the log daemon accepts without truncation.
- * @return LOGGER_ENTRY_MAX_PAYLOAD.
- */
- private static native int logger_entry_max_payload_native();
-
- /**
- * Helper function for long messages. Uses the LineBreakBufferedWriter to break
- * up long messages and stacktraces along newlines, but tries to write in large
- * chunks. This is to avoid truncation.
- * @hide
- */
- public static int printlns(int bufID, int priority, String tag, String msg,
- Throwable tr) {
- ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
- // Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
- // and the length of the tag.
- // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
- // is too expensive to compute that ahead of time.
- int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base.
- - 2 // Two terminators.
- - (tag != null ? tag.length() : 0) // Tag length.
- - 32; // Some slack.
- // At least assume you can print *some* characters (tag is not too large).
- bufferSize = Math.max(bufferSize, 100);
-
- LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);
-
- lbbw.println(msg);
-
- if (tr != null) {
- // This is to reduce the amount of log spew that apps do in the non-error
- // condition of the network being unavailable.
- Throwable t = tr;
- while (t != null) {
- if (t instanceof UnknownHostException) {
- break;
- }
- if (t instanceof DeadSystemException) {
- lbbw.println("DeadSystemException: The system died; "
- + "earlier logs will point to the root cause");
- break;
- }
- t = t.getCause();
- }
- if (t == null) {
- tr.printStackTrace(lbbw);
- }
- }
-
- lbbw.flush();
-
- return logWriter.getWritten();
- }
-
- /**
- * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
- * a JNI call during logging.
- */
- static class PreloadHolder {
- public final static int LOGGER_ENTRY_MAX_PAYLOAD =
- logger_entry_max_payload_native();
- }
-
- /**
- * Helper class to write to the logcat. Different from LogWriter, this writes
- * the whole given buffer and does not break along newlines.
- */
- private static class ImmediateLogWriter extends Writer {
-
- private int bufID;
- private int priority;
- private String tag;
-
- private int written = 0;
-
- /**
- * Create a writer that immediately writes to the log, using the given
- * parameters.
- */
- public ImmediateLogWriter(int bufID, int priority, String tag) {
- this.bufID = bufID;
- this.priority = priority;
- this.tag = tag;
- }
-
- public int getWritten() {
- return written;
- }
-
- @Override
- public void write(char[] cbuf, int off, int len) {
- // Note: using String here has a bit of overhead as a Java object is created,
- // but using the char[] directly is not easier, as it needs to be translated
- // to a C char[] for logging.
- written += println_native(bufID, priority, tag, new String(cbuf, off, len));
- }
-
- @Override
- public void flush() {
- // Ignored.
- }
-
- @Override
- public void close() {
- // Ignored.
- }
+ /** @hide */ @SuppressWarnings("unused")
+ public static int println(int bufID,
+ int priority, String tag, String msg) {
+ return 0;
}
}
diff --git a/android/util/LruCache.java b/android/util/LruCache.java
index 40154880..52086065 100644
--- a/android/util/LruCache.java
+++ b/android/util/LruCache.java
@@ -20,6 +20,10 @@ 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
@@ -87,8 +91,9 @@ public class LruCache<K, V> {
/**
* Sets the size of the cache.
- *
* @param maxSize The new maximum size.
+ *
+ * @hide
*/
public void resize(int maxSize) {
if (maxSize <= 0) {
@@ -185,13 +190,10 @@ 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.
*/
- public void trimToSize(int maxSize) {
+ private void trimToSize(int maxSize) {
while (true) {
K key;
V value;
@@ -205,7 +207,16 @@ public class LruCache<K, V> {
break;
}
- Map.Entry<K, V> toEvict = map.eldest();
+ // 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
+
if (toEvict == null) {
break;
}
diff --git a/android/util/StatsLog.java b/android/util/StatsLog.java
deleted file mode 100644
index 0be1a8cf..00000000
--- a/android/util/StatsLog.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-/**
- * Logging access for platform metrics.
- *
- * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})!
- * These diagnostic stats are for system integrators, not application authors.
- *
- * <p>Stats use integer tag codes.
- * They carry a payload of one or more int, long, or String values.
- * @hide
- */
-public class StatsLog {
- /** @hide */ public StatsLog() {}
-
- private static final String TAG = "StatsLog";
-
- // We assume that the native methods deal with any concurrency issues.
-
- /**
- * Records an stats log message.
- * @param tag The stats type tag code
- * @param value A value to log
- * @return The number of bytes written
- */
- public static native int writeInt(int tag, int value);
-
- /**
- * Records an stats log message.
- * @param tag The stats type tag code
- * @param value A value to log
- * @return The number of bytes written
- */
- public static native int writeLong(int tag, long value);
-
- /**
- * Records an stats log message.
- * @param tag The stats type tag code
- * @param value A value to log
- * @return The number of bytes written
- */
- public static native int writeFloat(int tag, float value);
-
- /**
- * Records an stats log message.
- * @param tag The stats type tag code
- * @param str A value to log
- * @return The number of bytes written
- */
- public static native int writeString(int tag, String str);
-
- /**
- * Records an stats log message.
- * @param tag The stats type tag code
- * @param list A list of values to log. All values should
- * be of type int, long, float or String.
- * @return The number of bytes written
- */
- public static native int writeArray(int tag, Object... list);
-}
diff --git a/android/util/StatsLogKey.java b/android/util/StatsLogKey.java
deleted file mode 100644
index 9ad0a23d..00000000
--- a/android/util/StatsLogKey.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// 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/StatsLogValue.java b/android/util/StatsLogValue.java
deleted file mode 100644
index 05b9d933..00000000
--- a/android/util/StatsLogValue.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// 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/view/SurfaceControl.java b/android/view/SurfaceControl.java
index 31daefff..54825895 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -21,15 +21,22 @@ import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import android.annotation.Size;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
+import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Binder;
+import android.os.Debug;
import android.os.IBinder;
import android.util.Log;
import android.view.Surface.OutOfResourcesException;
import dalvik.system.CloseGuard;
+import java.io.Closeable;
+
+import libcore.util.NativeAllocationRegistry;
+
/**
* SurfaceControl
* @hide
@@ -54,25 +61,34 @@ public class SurfaceControl {
Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
boolean allLayers, boolean useIdentityTransform);
- private static native void nativeOpenTransaction();
- private static native void nativeCloseTransaction(boolean sync);
- private static native void nativeSetAnimationTransaction();
-
- private static native void nativeSetLayer(long nativeObject, int zorder);
- private static native void nativeSetRelativeLayer(long nativeObject, IBinder relativeTo,
- int zorder);
- private static native void nativeSetPosition(long nativeObject, float x, float y);
- private static native void nativeSetGeometryAppliesWithResize(long nativeObject);
- 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,
+ private static native long nativeCreateTransaction();
+ private static native long nativeGetNativeTransactionFinalizer();
+ private static native void nativeApplyTransaction(long transactionObj, boolean sync);
+ private static native void nativeSetAnimationTransaction(long transactionObj);
+
+ private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder);
+ private static native void nativeSetRelativeLayer(long transactionObj, long nativeObject,
+ IBinder relativeTo, int zorder);
+ private static native void nativeSetPosition(long transactionObj, long nativeObject,
+ float x, float y);
+ private static native void nativeSetGeometryAppliesWithResize(long transactionObj,
+ long nativeObject);
+ private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h);
+ private static native void nativeSetTransparentRegionHint(long transactionObj,
+ long nativeObject, Region region);
+ private static native void nativeSetAlpha(long transactionObj, long nativeObject, float alpha);
+ private static native void nativeSetMatrix(long transactionObj, long nativeObject,
+ float dsdx, float dtdx,
float dtdy, float dsdy);
- private static native void nativeSetFlags(long nativeObject, int flags, int mask);
- private static native void nativeSetWindowCrop(long nativeObject, int l, int t, int r, int b);
- private static native void nativeSetFinalCrop(long nativeObject, int l, int t, int r, int b);
- private static native void nativeSetLayerStack(long nativeObject, int layerStack);
+ private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color);
+ private static native void nativeSetFlags(long transactionObj, long nativeObject,
+ int flags, int mask);
+ private static native void nativeSetWindowCrop(long transactionObj, long nativeObject,
+ int l, int t, int r, int b);
+ private static native void nativeSetFinalCrop(long transactionObj, long nativeObject,
+ int l, int t, int r, int b);
+ private static native void nativeSetLayerStack(long transactionObj, long nativeObject,
+ int layerStack);
private static native boolean nativeClearContentFrameStats(long nativeObject);
private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -82,15 +98,16 @@ public class SurfaceControl {
private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
private static native IBinder nativeCreateDisplay(String name, boolean secure);
private static native void nativeDestroyDisplay(IBinder displayToken);
- private static native void nativeSetDisplaySurface(
+ private static native void nativeSetDisplaySurface(long transactionObj,
IBinder displayToken, long nativeSurfaceObject);
- private static native void nativeSetDisplayLayerStack(
+ private static native void nativeSetDisplayLayerStack(long transactionObj,
IBinder displayToken, int layerStack);
- private static native void nativeSetDisplayProjection(
+ private static native void nativeSetDisplayProjection(long transactionObj,
IBinder displayToken, int orientation,
int l, int t, int r, int b,
int L, int T, int R, int B);
- private static native void nativeSetDisplaySize(IBinder displayToken, int width, int height);
+ private static native void nativeSetDisplaySize(long transactionObj, IBinder displayToken,
+ int width, int height);
private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs(
IBinder displayToken);
private static native int nativeGetActiveConfig(IBinder displayToken);
@@ -101,16 +118,17 @@ public class SurfaceControl {
int colorMode);
private static native void nativeSetDisplayPowerMode(
IBinder displayToken, int mode);
- private static native void nativeDeferTransactionUntil(long nativeObject,
+ private static native void nativeDeferTransactionUntil(long transactionObj, long nativeObject,
IBinder handle, long frame);
- private static native void nativeDeferTransactionUntilSurface(long nativeObject,
+ private static native void nativeDeferTransactionUntilSurface(long transactionObj,
+ long nativeObject,
long surfaceObject, long frame);
- private static native void nativeReparentChildren(long nativeObject,
+ private static native void nativeReparentChildren(long transactionObj, long nativeObject,
IBinder handle);
- private static native void nativeReparent(long nativeObject,
+ private static native void nativeReparent(long transactionObj, long nativeObject,
IBinder parentHandle);
- private static native void nativeSeverChildren(long nativeObject);
- private static native void nativeSetOverrideScalingMode(long nativeObject,
+ private static native void nativeSeverChildren(long transactionObj, long nativeObject);
+ private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
int scalingMode);
private static native IBinder nativeGetHandle(long nativeObject);
private static native boolean nativeGetTransformToDisplayInverse(long nativeObject);
@@ -122,6 +140,9 @@ public class SurfaceControl {
private final String mName;
long mNativeObject; // package visibility only for Surface.java access
+ static Transaction sGlobalTransaction;
+ static long sTransactionNestCount = 0;
+
/* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
/**
@@ -377,11 +398,6 @@ public class SurfaceControl {
}
}
- @Override
- public String toString() {
- return "Surface(name=" + mName + ")";
- }
-
/**
* Release the local reference to the server-side surface.
* Always call release() when you're done with a Surface.
@@ -429,102 +445,141 @@ public class SurfaceControl {
/** start a transaction */
public static void openTransaction() {
- nativeOpenTransaction();
+ synchronized (SurfaceControl.class) {
+ if (sGlobalTransaction == null) {
+ sGlobalTransaction = new Transaction();
+ }
+ synchronized(SurfaceControl.class) {
+ sTransactionNestCount++;
+ }
+ }
+ }
+
+ private static void closeTransaction(boolean sync) {
+ synchronized(SurfaceControl.class) {
+ if (sTransactionNestCount == 0) {
+ Log.e(TAG, "Call to SurfaceControl.closeTransaction without matching openTransaction");
+ } else if (--sTransactionNestCount > 0) {
+ return;
+ }
+ sGlobalTransaction.apply(sync);
+ }
}
/** end a transaction */
public static void closeTransaction() {
- nativeCloseTransaction(false);
+ closeTransaction(false);
}
public static void closeTransactionSync() {
- nativeCloseTransaction(true);
+ closeTransaction(true);
}
public void deferTransactionUntil(IBinder handle, long frame) {
if (frame > 0) {
- nativeDeferTransactionUntil(mNativeObject, handle, frame);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.deferTransactionUntil(this, handle, frame);
+ }
}
}
public void deferTransactionUntil(Surface barrier, long frame) {
if (frame > 0) {
- nativeDeferTransactionUntilSurface(mNativeObject, barrier.mNativeObject, frame);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.deferTransactionUntilSurface(this, barrier, frame);
+ }
}
}
public void reparentChildren(IBinder newParentHandle) {
- nativeReparentChildren(mNativeObject, newParentHandle);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.reparentChildren(this, newParentHandle);
+ }
}
- /** Re-parents this layer to a new parent. */
public void reparent(IBinder newParentHandle) {
- nativeReparent(mNativeObject, newParentHandle);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.reparent(this, newParentHandle);
+ }
}
public void detachChildren() {
- nativeSeverChildren(mNativeObject);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.detachChildren(this);
+ }
}
public void setOverrideScalingMode(int scalingMode) {
checkNotReleased();
- nativeSetOverrideScalingMode(mNativeObject, scalingMode);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setOverrideScalingMode(this, scalingMode);
+ }
}
public IBinder getHandle() {
return nativeGetHandle(mNativeObject);
}
- /** flag the transaction as an animation */
public static void setAnimationTransaction() {
- nativeSetAnimationTransaction();
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setAnimationTransaction();
+ }
}
public void setLayer(int zorder) {
checkNotReleased();
- nativeSetLayer(mNativeObject, zorder);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setLayer(this, zorder);
+ }
}
public void setRelativeLayer(IBinder relativeTo, int zorder) {
checkNotReleased();
- nativeSetRelativeLayer(mNativeObject, relativeTo, zorder);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder);
+ }
}
public void setPosition(float x, float y) {
checkNotReleased();
- nativeSetPosition(mNativeObject, x, y);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setPosition(this, x, y);
+ }
}
- /**
- * If the buffer size changes in this transaction, position and crop updates specified
- * in this transaction will not complete until a buffer of the new size
- * arrives. As transform matrix and size are already frozen in this fashion,
- * this enables totally freezing the surface until the resize has completed
- * (at which point the geometry influencing aspects of this transaction will then occur)
- */
public void setGeometryAppliesWithResize() {
checkNotReleased();
- nativeSetGeometryAppliesWithResize(mNativeObject);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setGeometryAppliesWithResize(this);
+ }
}
public void setSize(int w, int h) {
checkNotReleased();
- nativeSetSize(mNativeObject, w, h);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setSize(this, w, h);
+ }
}
public void hide() {
checkNotReleased();
- nativeSetFlags(mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.hide(this);
+ }
}
public void show() {
checkNotReleased();
- nativeSetFlags(mNativeObject, 0, SURFACE_HIDDEN);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.show(this);
+ }
}
public void setTransparentRegionHint(Region region) {
checkNotReleased();
- nativeSetTransparentRegionHint(mNativeObject, region);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setTransparentRegionHint(this, region);
+ }
}
public boolean clearContentFrameStats() {
@@ -545,80 +600,70 @@ public class SurfaceControl {
return nativeGetAnimationFrameStats(outStats);
}
- /**
- * Sets an alpha value for the entire Surface. This value is combined with the
- * per-pixel alpha. It may be used with opaque Surfaces.
- */
public void setAlpha(float alpha) {
checkNotReleased();
- nativeSetAlpha(mNativeObject, alpha);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setAlpha(this, 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);
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setColor(this, color);
+ }
}
public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
checkNotReleased();
- nativeSetMatrix(mNativeObject, dsdx, dtdx, dtdy, dsdy);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setMatrix(this, dsdx, dtdx, dtdy, dsdy);
+ }
}
public void setWindowCrop(Rect crop) {
checkNotReleased();
- if (crop != null) {
- nativeSetWindowCrop(mNativeObject,
- crop.left, crop.top, crop.right, crop.bottom);
- } else {
- nativeSetWindowCrop(mNativeObject, 0, 0, 0, 0);
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setWindowCrop(this, crop);
}
}
public void setFinalCrop(Rect crop) {
checkNotReleased();
- if (crop != null) {
- nativeSetFinalCrop(mNativeObject,
- crop.left, crop.top, crop.right, crop.bottom);
- } else {
- nativeSetFinalCrop(mNativeObject, 0, 0, 0, 0);
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setFinalCrop(this, crop);
}
}
public void setLayerStack(int layerStack) {
checkNotReleased();
- nativeSetLayerStack(mNativeObject, layerStack);
+ synchronized(SurfaceControl.class) {
+ sGlobalTransaction.setLayerStack(this, layerStack);
+ }
}
- /**
- * Sets the opacity of the surface. Setting the flag is equivalent to creating the
- * Surface with the {@link #OPAQUE} flag.
- */
public void setOpaque(boolean isOpaque) {
checkNotReleased();
- if (isOpaque) {
- nativeSetFlags(mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
- } else {
- nativeSetFlags(mNativeObject, 0, SURFACE_OPAQUE);
+
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setOpaque(this, isOpaque);
}
}
- /**
- * Sets the security of the surface. Setting the flag is equivalent to creating the
- * Surface with the {@link #SECURE} flag.
- */
public void setSecure(boolean isSecure) {
checkNotReleased();
- if (isSecure) {
- nativeSetFlags(mNativeObject, SECURE, SECURE);
- } else {
- nativeSetFlags(mNativeObject, 0, SECURE);
+
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setSecure(this, isSecure);
}
}
+ @Override
+ public String toString() {
+ return "Surface(name=" + mName + ")/@0x" +
+ Integer.toHexString(System.identityHashCode(this));
+ }
+
/*
* set display parameters.
* needs to be inside open/closeTransaction block
@@ -741,50 +786,28 @@ public class SurfaceControl {
public static void setDisplayProjection(IBinder displayToken,
int orientation, Rect layerStackRect, Rect displayRect) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- if (layerStackRect == null) {
- throw new IllegalArgumentException("layerStackRect must not be null");
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setDisplayProjection(displayToken, orientation,
+ layerStackRect, displayRect);
}
- if (displayRect == null) {
- throw new IllegalArgumentException("displayRect must not be null");
- }
- nativeSetDisplayProjection(displayToken, orientation,
- layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
- displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
}
public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setDisplayLayerStack(displayToken, layerStack);
}
- nativeSetDisplayLayerStack(displayToken, layerStack);
}
public static void setDisplaySurface(IBinder displayToken, Surface surface) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
-
- if (surface != null) {
- synchronized (surface.mLock) {
- nativeSetDisplaySurface(displayToken, surface.mNativeObject);
- }
- } else {
- nativeSetDisplaySurface(displayToken, 0);
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setDisplaySurface(displayToken, surface);
}
}
public static void setDisplaySize(IBinder displayToken, int width, int height) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- if (width <= 0 || height <= 0) {
- throw new IllegalArgumentException("width and height must be positive");
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setDisplaySize(displayToken, width, height);
}
-
- nativeSetDisplaySize(displayToken, width, height);
}
public static Display.HdrCapabilities getHdrCapabilities(IBinder displayToken) {
@@ -946,4 +969,261 @@ public class SurfaceControl {
nativeScreenshot(display, consumer, sourceCrop, width, height,
minLayer, maxLayer, allLayers, useIdentityTransform);
}
+
+ public static class Transaction implements Closeable {
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ Transaction.class.getClassLoader(),
+ nativeGetNativeTransactionFinalizer(), 512);
+ private long mNativeObject;
+
+ Runnable mFreeNativeResources;
+
+ public Transaction() {
+ mNativeObject = nativeCreateTransaction();
+ mFreeNativeResources
+ = sRegistry.registerNativeAllocation(this, mNativeObject);
+ }
+
+ /**
+ * Apply the transaction, clearing it's state, and making it usable
+ * as a new transaction.
+ */
+ public void apply() {
+ apply(false);
+ }
+
+ /**
+ * Close the transaction, if the transaction was not already applied this will cancel the
+ * transaction.
+ */
+ @Override
+ public void close() {
+ mFreeNativeResources.run();
+ mNativeObject = 0;
+ }
+
+ /**
+ * Jankier version of apply. Avoid use (b/28068298).
+ */
+ public void apply(boolean sync) {
+ nativeApplyTransaction(mNativeObject, sync);
+ }
+
+ public Transaction show(SurfaceControl sc) {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
+ return this;
+ }
+
+ public Transaction hide(SurfaceControl sc) {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
+ return this;
+ }
+
+ public Transaction setPosition(SurfaceControl sc, float x, float y) {
+ nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
+ return this;
+ }
+
+ public Transaction setSize(SurfaceControl sc, int w, int h) {
+ nativeSetSize(mNativeObject, sc.mNativeObject,
+ w, h);
+ return this;
+ }
+
+ public Transaction setLayer(SurfaceControl sc, int z) {
+ nativeSetLayer(mNativeObject, sc.mNativeObject, z);
+ return this;
+ }
+
+ public Transaction setRelativeLayer(SurfaceControl sc, IBinder relativeTo, int z) {
+ nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
+ relativeTo, z);
+ return this;
+ }
+
+ public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
+ nativeSetTransparentRegionHint(mNativeObject,
+ sc.mNativeObject, transparentRegion);
+ return this;
+ }
+
+ public Transaction setAlpha(SurfaceControl sc, float alpha) {
+ nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
+ return this;
+ }
+
+ public Transaction setMatrix(SurfaceControl sc,
+ float dsdx, float dtdx, float dtdy, float dsdy) {
+ nativeSetMatrix(mNativeObject, sc.mNativeObject,
+ dsdx, dtdx, dtdy, dsdy);
+ return this;
+ }
+
+ public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
+ if (crop != null) {
+ nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
+ crop.left, crop.top, crop.right, crop.bottom);
+ } else {
+ nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
+ }
+
+ return this;
+ }
+
+ public Transaction setFinalCrop(SurfaceControl sc, Rect crop) {
+ if (crop != null) {
+ nativeSetFinalCrop(mNativeObject, sc.mNativeObject,
+ crop.left, crop.top, crop.right, crop.bottom);
+ } else {
+ nativeSetFinalCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
+ }
+
+ return this;
+ }
+
+ public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
+ nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
+ return this;
+ }
+
+ public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) {
+ nativeDeferTransactionUntil(mNativeObject, sc.mNativeObject, handle, frameNumber);
+ return this;
+ }
+
+ public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
+ long frameNumber) {
+ nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject,
+ barrierSurface.mNativeObject, frameNumber);
+ return this;
+ }
+
+ public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) {
+ nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle);
+ return this;
+ }
+
+ /** Re-parents a specific child layer to a new parent */
+ public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
+ nativeReparent(mNativeObject, sc.mNativeObject,
+ newParentHandle);
+ return this;
+ }
+
+ public Transaction detachChildren(SurfaceControl sc) {
+ nativeSeverChildren(mNativeObject, sc.mNativeObject);
+ return this;
+ }
+
+ public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) {
+ nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject,
+ overrideScalingMode);
+ return this;
+ }
+
+ /**
+ * Sets a color for the Surface.
+ * @param color A float array with three values to represent r, g, b in range [0..1]
+ */
+ public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
+ nativeSetColor(mNativeObject, sc.mNativeObject, color);
+ return this;
+ }
+
+ /**
+ * If the buffer size changes in this transaction, position and crop updates specified
+ * in this transaction will not complete until a buffer of the new size
+ * arrives. As transform matrix and size are already frozen in this fashion,
+ * this enables totally freezing the surface until the resize has completed
+ * (at which point the geometry influencing aspects of this transaction will then occur)
+ */
+ public Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
+ nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject);
+ return this;
+ }
+
+ /**
+ * Sets the security of the surface. Setting the flag is equivalent to creating the
+ * Surface with the {@link #SECURE} flag.
+ */
+ Transaction setSecure(SurfaceControl sc, boolean isSecure) {
+ if (isSecure) {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
+ } else {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SECURE);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the opacity of the surface. Setting the flag is equivalent to creating the
+ * Surface with the {@link #OPAQUE} flag.
+ */
+ public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+ if (isOpaque) {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, OPAQUE, OPAQUE);
+ } else {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, 0, OPAQUE);
+ }
+ return this;
+ }
+
+ public Transaction setDisplaySurface(IBinder displayToken, Surface surface) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+
+ if (surface != null) {
+ synchronized (surface.mLock) {
+ nativeSetDisplaySurface(mNativeObject, displayToken, surface.mNativeObject);
+ }
+ } else {
+ nativeSetDisplaySurface(mNativeObject, displayToken, 0);
+ }
+ return this;
+ }
+
+ public Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeSetDisplayLayerStack(mNativeObject, displayToken, layerStack);
+ return this;
+ }
+
+ public Transaction setDisplayProjection(IBinder displayToken,
+ int orientation, Rect layerStackRect, Rect displayRect) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (layerStackRect == null) {
+ throw new IllegalArgumentException("layerStackRect must not be null");
+ }
+ if (displayRect == null) {
+ throw new IllegalArgumentException("displayRect must not be null");
+ }
+ nativeSetDisplayProjection(mNativeObject, displayToken, orientation,
+ layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
+ displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
+ return this;
+ }
+
+ public Transaction setDisplaySize(IBinder displayToken, int width, int height) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("width and height must be positive");
+ }
+
+ nativeSetDisplaySize(mNativeObject, displayToken, width, height);
+ return this;
+ }
+
+ /** flag the transaction as an animation */
+ public Transaction setAnimationTransaction() {
+ nativeSetAnimationTransaction(mNativeObject);
+ return this;
+ }
+ }
}
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index 462dad3f..ebb2af45 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -16,1208 +16,115 @@
package android.view;
-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 com.android.layoutlib.bridge.MockView;
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;
/**
- * 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.
+ * 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.
*
- * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
- * which can be retrieved by calling {@link #getHolder}.
+ * TODO: generate automatically.
*
- * <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 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 class SurfaceView extends MockView {
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 defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
+ public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
}
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) {
- 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;
+ return false;
}
- @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;
- }
- }
-
- private void updateOpaqueFlag() {
- if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
- mSurfaceFlags |= SurfaceControl.OPAQUE;
- } else {
- mSurfaceFlags &= ~SurfaceControl.OPAQUE;
- }
}
- 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;
+ public SurfaceHolder getHolder() {
+ return mSurfaceHolder;
}
- private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
- private static final String LOG_TAG = "SurfaceHolder";
+ private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
@Override
public boolean isCreating() {
- return mIsCreating;
+ return false;
}
@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
- @Deprecated
- public void setType(int type) { }
+ 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 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 inOutDirty) {
- return internalLockCanvas(inOutDirty, false);
+ return null;
}
@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();
-
+ public Canvas lockCanvas(Rect dirty) {
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 mSurface;
+ return null;
}
@Override
public Rect getSurfaceFrame() {
- return mSurfaceFrame;
+ return null;
}
};
-
- 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 b6be2961..c043dcac 100644
--- a/android/view/View.java
+++ b/android/view/View.java
@@ -1448,17 +1448,59 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* <p>Enables low quality mode for the drawing cache.</p>
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int DRAWING_CACHE_QUALITY_LOW = 0x00080000;
/**
* <p>Enables high quality mode for the drawing cache.</p>
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int DRAWING_CACHE_QUALITY_HIGH = 0x00100000;
/**
* <p>Enables automatic quality mode for the drawing cache.</p>
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int DRAWING_CACHE_QUALITY_AUTO = 0x00000000;
private static final int[] DRAWING_CACHE_QUALITY_FLAGS = {
@@ -2300,9 +2342,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final int PFLAG_HOVERED = 0x10000000;
/**
- * no longer needed, should be reused
+ * Flag set by {@link AutofillManager} if it needs to be notified when this view is clicked.
*/
- private static final int PFLAG_DOES_NOTHING_REUSE_PLEASE = 0x20000000;
+ private static final int PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK = 0x20000000;
/** {@hide} */
static final int PFLAG_ACTIVATED = 0x40000000;
@@ -6355,6 +6397,42 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return null;
}
+ /** @hide */
+ public void setNotifyAutofillManagerOnClick(boolean notify) {
+ if (notify) {
+ mPrivateFlags |= PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+ } else {
+ mPrivateFlags &= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+ }
+ }
+
+ private void notifyAutofillManagerOnClick() {
+ if ((mPrivateFlags & PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK) != 0) {
+ try {
+ getAutofillManager().notifyViewClicked(this);
+ } finally {
+ // Set it to already called so it's not called twice when called by
+ // performClickInternal()
+ mPrivateFlags |= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+ }
+ }
+ }
+
+ /**
+ * Entry point for {@link #performClick()} - other methods on View should call it instead of
+ * {@code performClick()} directly to make sure the autofill manager is notified when
+ * necessary (as subclasses could extend {@code performClick()} without calling the parent's
+ * method).
+ */
+ private boolean performClickInternal() {
+ // Must notify autofill manager before performing the click actions to avoid scenarios where
+ // the app has a click listener that changes the state of views the autofill service might
+ // be interested on.
+ notifyAutofillManagerOnClick();
+
+ return performClick();
+ }
+
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
@@ -6363,7 +6441,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
+ // NOTE: other methods on View should not call this method directly, but performClickInternal()
+ // instead, to guarantee that the autofill manager is notified when necessary (as subclasses
+ // could extend this method without calling super.performClick()).
public boolean performClick() {
+ // We still need to call this method to handle the cases where performClick() was called
+ // externally, instead of through performClickInternal()
+ notifyAutofillManagerOnClick();
+
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
@@ -8907,7 +8992,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #isDrawingCacheEnabled()
*
* @attr ref android.R.styleable#View_drawingCacheQuality
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
@DrawingCacheQuality
public int getDrawingCacheQuality() {
return mViewFlags & DRAWING_CACHE_QUALITY_MASK;
@@ -8925,7 +9024,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #isDrawingCacheEnabled()
*
* @attr ref android.R.styleable#View_drawingCacheQuality
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void setDrawingCacheQuality(@DrawingCacheQuality int quality) {
setFlags(quality, DRAWING_CACHE_QUALITY_MASK);
}
@@ -11433,7 +11546,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK: {
if (isClickable()) {
- performClick();
+ performClickInternal();
return true;
}
} break;
@@ -12545,7 +12658,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// This is a tap, so remove the longpress check
removeLongPressCallback();
if (!event.isCanceled()) {
- return performClick();
+ return performClickInternal();
}
}
}
@@ -13117,7 +13230,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
- performClick();
+ performClickInternal();
}
}
}
@@ -18103,7 +18216,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #getDrawingCache()
* @see #buildDrawingCache()
* @see #setLayerType(int, android.graphics.Paint)
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void setDrawingCacheEnabled(boolean enabled) {
mCachingFailed = false;
setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED);
@@ -18116,7 +18243,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see #setDrawingCacheEnabled(boolean)
* @see #getDrawingCache()
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
@ViewDebug.ExportedProperty(category = "drawing")
public boolean isDrawingCacheEnabled() {
return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED;
@@ -18130,10 +18271,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@SuppressWarnings({"UnusedDeclaration"})
public void outputDirtyFlags(String indent, boolean clear, int clearMask) {
- Log.d("View", indent + this + " DIRTY(" + (mPrivateFlags & View.PFLAG_DIRTY_MASK) +
- ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID(" +
- (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID) +
- ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
+ Log.d(VIEW_LOG_TAG, indent + this + " DIRTY("
+ + (mPrivateFlags & View.PFLAG_DIRTY_MASK)
+ + ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID("
+ + (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID)
+ + ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
if (clear) {
mPrivateFlags &= clearMask;
}
@@ -18257,7 +18399,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return A non-scaled bitmap representing this view or null if cache is disabled.
*
* @see #getDrawingCache(boolean)
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public Bitmap getDrawingCache() {
return getDrawingCache(false);
}
@@ -18288,7 +18444,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #isDrawingCacheEnabled()
* @see #buildDrawingCache(boolean)
* @see #destroyDrawingCache()
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public Bitmap getDrawingCache(boolean autoScale) {
if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
return null;
@@ -18308,7 +18478,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setDrawingCacheEnabled(boolean)
* @see #buildDrawingCache()
* @see #getDrawingCache()
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void destroyDrawingCache() {
if (mDrawingCache != null) {
mDrawingCache.recycle();
@@ -18330,7 +18514,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setDrawingCacheEnabled(boolean)
* @see #buildDrawingCache()
* @see #getDrawingCache()
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void setDrawingCacheBackgroundColor(@ColorInt int color) {
if (color != mDrawingCacheBackgroundColor) {
mDrawingCacheBackgroundColor = color;
@@ -18342,7 +18540,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setDrawingCacheBackgroundColor(int)
*
* @return The background color to used for the drawing cache's bitmap
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
@ColorInt
public int getDrawingCacheBackgroundColor() {
return mDrawingCacheBackgroundColor;
@@ -18352,7 +18564,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>Calling this method is equivalent to calling <code>buildDrawingCache(false)</code>.</p>
*
* @see #buildDrawingCache(boolean)
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void buildDrawingCache() {
buildDrawingCache(false);
}
@@ -18379,7 +18605,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see #getDrawingCache()
* @see #destroyDrawingCache()
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void buildDrawingCache(boolean autoScale) {
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
mDrawingCache == null : mUnscaledDrawingCache == null)) {
@@ -19812,7 +20052,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
boolean changed = false;
if (DBG) {
- Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
@@ -24858,7 +25098,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private final class PerformClick implements Runnable {
@Override
public void run() {
- performClick();
+ performClickInternal();
}
}
diff --git a/android/view/ViewDebug.java b/android/view/ViewDebug.java
index 3426485e..afa94131 100644
--- a/android/view/ViewDebug.java
+++ b/android/view/ViewDebug.java
@@ -528,84 +528,23 @@ public class ViewDebug {
/** @hide */
public static void profileViewAndChildren(final View view, BufferedWriter out)
throws IOException {
- profileViewAndChildren(view, out, true);
+ RenderNode node = RenderNode.create("ViewDebug", null);
+ profileViewAndChildren(view, node, out, true);
+ node.destroy();
}
- private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root)
- throws IOException {
-
+ private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out,
+ boolean root) throws IOException {
long durationMeasure =
(root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0)
- ? profileViewOperation(view, new ViewOperation<Void>() {
- public Void[] pre() {
- forceLayout(view);
- return null;
- }
-
- private void forceLayout(View view) {
- view.forceLayout();
- if (view instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) view;
- final int count = group.getChildCount();
- for (int i = 0; i < count; i++) {
- forceLayout(group.getChildAt(i));
- }
- }
- }
-
- public void run(Void... data) {
- view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
- }
-
- public void post(Void... data) {
- }
- })
- : 0;
+ ? profileViewMeasure(view) : 0;
long durationLayout =
(root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0)
- ? profileViewOperation(view, new ViewOperation<Void>() {
- public Void[] pre() {
- return null;
- }
-
- public void run(Void... data) {
- view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
- }
-
- public void post(Void... data) {
- }
- }) : 0;
+ ? profileViewLayout(view) : 0;
long durationDraw =
(root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0)
- ? profileViewOperation(view, new ViewOperation<Object>() {
- public Object[] pre() {
- final DisplayMetrics metrics =
- (view != null && view.getResources() != null) ?
- view.getResources().getDisplayMetrics() : null;
- final Bitmap bitmap = metrics != null ?
- Bitmap.createBitmap(metrics, metrics.widthPixels,
- metrics.heightPixels, Bitmap.Config.RGB_565) : null;
- final Canvas canvas = bitmap != null ? new Canvas(bitmap) : null;
- return new Object[] {
- bitmap, canvas
- };
- }
+ ? profileViewDraw(view, node) : 0;
- public void run(Object... data) {
- if (data[1] != null) {
- view.draw((Canvas) data[1]);
- }
- }
-
- public void post(Object... data) {
- if (data[1] != null) {
- ((Canvas) data[1]).setBitmap(null);
- }
- if (data[0] != null) {
- ((Bitmap) data[0]).recycle();
- }
- }
- }) : 0;
out.write(String.valueOf(durationMeasure));
out.write(' ');
out.write(String.valueOf(durationLayout));
@@ -616,34 +555,86 @@ public class ViewDebug {
ViewGroup group = (ViewGroup) view;
final int count = group.getChildCount();
for (int i = 0; i < count; i++) {
- profileViewAndChildren(group.getChildAt(i), out, false);
+ profileViewAndChildren(group.getChildAt(i), node, out, false);
+ }
+ }
+ }
+
+ private static long profileViewMeasure(final View view) {
+ return profileViewOperation(view, new ViewOperation() {
+ @Override
+ public void pre() {
+ forceLayout(view);
+ }
+
+ private void forceLayout(View view) {
+ view.forceLayout();
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ forceLayout(group.getChildAt(i));
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
+ }
+ });
+ }
+
+ private static long profileViewLayout(View view) {
+ return profileViewOperation(view,
+ () -> view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom));
+ }
+
+ private static long profileViewDraw(View view, RenderNode node) {
+ DisplayMetrics dm = view.getResources().getDisplayMetrics();
+ if (dm == null) {
+ return 0;
+ }
+
+ if (view.isHardwareAccelerated()) {
+ DisplayListCanvas canvas = node.start(dm.widthPixels, dm.heightPixels);
+ try {
+ return profileViewOperation(view, () -> view.draw(canvas));
+ } finally {
+ node.end(canvas);
+ }
+ } else {
+ Bitmap bitmap = Bitmap.createBitmap(
+ dm, dm.widthPixels, dm.heightPixels, Bitmap.Config.RGB_565);
+ Canvas canvas = new Canvas(bitmap);
+ try {
+ return profileViewOperation(view, () -> view.draw(canvas));
+ } finally {
+ canvas.setBitmap(null);
+ bitmap.recycle();
}
}
}
- interface ViewOperation<T> {
- T[] pre();
- void run(T... data);
- void post(T... data);
+ interface ViewOperation {
+ default void pre() {}
+
+ void run();
}
- private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
+ private static long profileViewOperation(View view, final ViewOperation operation) {
final CountDownLatch latch = new CountDownLatch(1);
final long[] duration = new long[1];
- view.post(new Runnable() {
- public void run() {
- try {
- T[] data = operation.pre();
- long start = Debug.threadCpuTimeNanos();
- //noinspection unchecked
- operation.run(data);
- duration[0] = Debug.threadCpuTimeNanos() - start;
- //noinspection unchecked
- operation.post(data);
- } finally {
- latch.countDown();
- }
+ view.post(() -> {
+ try {
+ operation.pre();
+ long start = Debug.threadCpuTimeNanos();
+ //noinspection unchecked
+ operation.run();
+ duration[0] = Debug.threadCpuTimeNanos() - start;
+ } finally {
+ latch.countDown();
}
});
diff --git a/android/view/ViewGroup.java b/android/view/ViewGroup.java
index b2e5a163..929beaea 100644
--- a/android/view/ViewGroup.java
+++ b/android/view/ViewGroup.java
@@ -421,22 +421,78 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Used to indicate that no drawing cache should be kept in memory.
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int PERSISTENT_NO_CACHE = 0x0;
/**
* Used to indicate that the animation drawing cache should be kept in memory.
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int PERSISTENT_ANIMATION_CACHE = 0x1;
/**
* Used to indicate that the scrolling drawing cache should be kept in memory.
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int PERSISTENT_SCROLLING_CACHE = 0x2;
/**
* Used to indicate that all drawing caches should be kept in memory.
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public static final int PERSISTENT_ALL_CACHES = 0x3;
// Layout Modes
@@ -3769,7 +3825,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* Enables or disables the drawing cache for each child of this view group.
*
* @param enabled true to enable the cache, false to dispose of it
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
protected void setChildrenDrawingCacheEnabled(boolean enabled) {
if (enabled || (mPersistentDrawingCache & PERSISTENT_ALL_CACHES) != PERSISTENT_ALL_CACHES) {
final View[] children = mChildren;
@@ -6331,7 +6401,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @return one or a combination of {@link #PERSISTENT_NO_CACHE},
* {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
* and {@link #PERSISTENT_ALL_CACHES}
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
@ViewDebug.ExportedProperty(category = "drawing", mapping = {
@ViewDebug.IntToString(from = PERSISTENT_NO_CACHE, to = "NONE"),
@ViewDebug.IntToString(from = PERSISTENT_ANIMATION_CACHE, to = "ANIMATION"),
@@ -6352,7 +6436,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE},
* {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
* and {@link #PERSISTENT_ALL_CACHES}
+ *
+ * @deprecated The view drawing cache was largely made obsolete with the introduction of
+ * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+ * layers are largely unnecessary and can easily result in a net loss in performance due to the
+ * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+ * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+ * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+ * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+ * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+ * software-rendered usages are discouraged and have compatibility issues with hardware-only
+ * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+ * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+ * reports or unit testing the {@link PixelCopy} API is recommended.
*/
+ @Deprecated
public void setPersistentDrawingCache(int drawingCacheToKeep) {
mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES;
}
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 71106ada..99438d87 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -72,6 +72,7 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MergedConfiguration;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.TypedValue;
import android.view.Surface.OutOfResourcesException;
@@ -1668,8 +1669,6 @@ public final class ViewRootImpl implements ViewParent,
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
- //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
-
} else {
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
@@ -2827,7 +2826,7 @@ public final class ViewRootImpl implements ViewParent,
try {
mWindowDrawCountDown.await();
} catch (InterruptedException e) {
- Log.e(mTag, "Window redraw count down interruped!");
+ Log.e(mTag, "Window redraw count down interrupted!");
}
mWindowDrawCountDown = null;
}
@@ -2897,8 +2896,6 @@ public final class ViewRootImpl implements ViewParent,
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
- int resizeAlpha = 0;
-
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
@@ -3469,6 +3466,7 @@ public final class ViewRootImpl implements ViewParent,
}
void dispatchDetachedFromWindow() {
+ mFirstInputStage.onDetachedFromWindow();
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
@@ -3731,266 +3729,273 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_INVALIDATE:
- ((View) msg.obj).invalidate();
- break;
- case MSG_INVALIDATE_RECT:
- final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
- info.target.invalidate(info.left, info.top, info.right, info.bottom);
- info.recycle();
- break;
- case MSG_PROCESS_INPUT_EVENTS:
- mProcessInputEventsScheduled = false;
- doProcessInputEvents();
- break;
- case MSG_DISPATCH_APP_VISIBILITY:
- handleAppVisibility(msg.arg1 != 0);
- break;
- case MSG_DISPATCH_GET_NEW_SURFACE:
- handleGetNewSurface();
- break;
- case MSG_RESIZED: {
- // Recycled in the fall through...
- SomeArgs args = (SomeArgs) msg.obj;
- if (mWinFrame.equals(args.arg1)
- && mPendingOverscanInsets.equals(args.arg5)
- && mPendingContentInsets.equals(args.arg2)
- && mPendingStableInsets.equals(args.arg6)
- && mPendingVisibleInsets.equals(args.arg3)
- && mPendingOutsets.equals(args.arg7)
- && mPendingBackDropFrame.equals(args.arg8)
- && args.arg4 == null
- && args.argi1 == 0
- && mDisplay.getDisplayId() == args.argi3) {
+ case MSG_INVALIDATE:
+ ((View) msg.obj).invalidate();
break;
- }
- } // fall through...
- case MSG_RESIZED_REPORT:
- if (mAdded) {
+ case MSG_INVALIDATE_RECT:
+ final View.AttachInfo.InvalidateInfo info =
+ (View.AttachInfo.InvalidateInfo) msg.obj;
+ info.target.invalidate(info.left, info.top, info.right, info.bottom);
+ info.recycle();
+ break;
+ case MSG_PROCESS_INPUT_EVENTS:
+ mProcessInputEventsScheduled = false;
+ doProcessInputEvents();
+ break;
+ case MSG_DISPATCH_APP_VISIBILITY:
+ handleAppVisibility(msg.arg1 != 0);
+ break;
+ case MSG_DISPATCH_GET_NEW_SURFACE:
+ handleGetNewSurface();
+ break;
+ case MSG_RESIZED: {
+ // Recycled in the fall through...
SomeArgs args = (SomeArgs) msg.obj;
-
- final int displayId = args.argi3;
- MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4;
- final boolean displayChanged = mDisplay.getDisplayId() != displayId;
-
- if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) {
- // If configuration changed - notify about that and, maybe, about move to
- // display.
- performConfigurationChange(mergedConfiguration, false /* force */,
- displayChanged ? displayId : INVALID_DISPLAY /* same display */);
- } else if (displayChanged) {
- // Moved to display without config change - report last applied one.
- onMovedToDisplay(displayId, mLastConfigurationFromResources);
+ if (mWinFrame.equals(args.arg1)
+ && mPendingOverscanInsets.equals(args.arg5)
+ && mPendingContentInsets.equals(args.arg2)
+ && mPendingStableInsets.equals(args.arg6)
+ && mPendingVisibleInsets.equals(args.arg3)
+ && mPendingOutsets.equals(args.arg7)
+ && mPendingBackDropFrame.equals(args.arg8)
+ && args.arg4 == null
+ && args.argi1 == 0
+ && mDisplay.getDisplayId() == args.argi3) {
+ break;
}
+ } // fall through...
+ case MSG_RESIZED_REPORT:
+ if (mAdded) {
+ SomeArgs args = (SomeArgs) msg.obj;
+
+ final int displayId = args.argi3;
+ MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4;
+ final boolean displayChanged = mDisplay.getDisplayId() != displayId;
+
+ if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) {
+ // If configuration changed - notify about that and, maybe,
+ // about move to display.
+ performConfigurationChange(mergedConfiguration, false /* force */,
+ displayChanged
+ ? displayId : INVALID_DISPLAY /* same display */);
+ } else if (displayChanged) {
+ // Moved to display without config change - report last applied one.
+ onMovedToDisplay(displayId, mLastConfigurationFromResources);
+ }
- final boolean framesChanged = !mWinFrame.equals(args.arg1)
- || !mPendingOverscanInsets.equals(args.arg5)
- || !mPendingContentInsets.equals(args.arg2)
- || !mPendingStableInsets.equals(args.arg6)
- || !mPendingVisibleInsets.equals(args.arg3)
- || !mPendingOutsets.equals(args.arg7);
-
- mWinFrame.set((Rect) args.arg1);
- mPendingOverscanInsets.set((Rect) args.arg5);
- mPendingContentInsets.set((Rect) args.arg2);
- mPendingStableInsets.set((Rect) args.arg6);
- mPendingVisibleInsets.set((Rect) args.arg3);
- mPendingOutsets.set((Rect) args.arg7);
- mPendingBackDropFrame.set((Rect) args.arg8);
- mForceNextWindowRelayout = args.argi1 != 0;
- mPendingAlwaysConsumeNavBar = args.argi2 != 0;
-
- args.recycle();
+ final boolean framesChanged = !mWinFrame.equals(args.arg1)
+ || !mPendingOverscanInsets.equals(args.arg5)
+ || !mPendingContentInsets.equals(args.arg2)
+ || !mPendingStableInsets.equals(args.arg6)
+ || !mPendingVisibleInsets.equals(args.arg3)
+ || !mPendingOutsets.equals(args.arg7);
+
+ mWinFrame.set((Rect) args.arg1);
+ mPendingOverscanInsets.set((Rect) args.arg5);
+ mPendingContentInsets.set((Rect) args.arg2);
+ mPendingStableInsets.set((Rect) args.arg6);
+ mPendingVisibleInsets.set((Rect) args.arg3);
+ mPendingOutsets.set((Rect) args.arg7);
+ mPendingBackDropFrame.set((Rect) args.arg8);
+ mForceNextWindowRelayout = args.argi1 != 0;
+ mPendingAlwaysConsumeNavBar = args.argi2 != 0;
+
+ args.recycle();
+
+ if (msg.what == MSG_RESIZED_REPORT) {
+ reportNextDraw();
+ }
- if (msg.what == MSG_RESIZED_REPORT) {
- reportNextDraw();
+ if (mView != null && framesChanged) {
+ forceLayout(mView);
+ }
+ requestLayout();
}
-
- if (mView != null && framesChanged) {
- forceLayout(mView);
+ break;
+ case MSG_WINDOW_MOVED:
+ if (mAdded) {
+ final int w = mWinFrame.width();
+ final int h = mWinFrame.height();
+ final int l = msg.arg1;
+ final int t = msg.arg2;
+ mWinFrame.left = l;
+ mWinFrame.right = l + w;
+ mWinFrame.top = t;
+ mWinFrame.bottom = t + h;
+
+ mPendingBackDropFrame.set(mWinFrame);
+ maybeHandleWindowMove(mWinFrame);
}
- requestLayout();
- }
- break;
- case MSG_WINDOW_MOVED:
- if (mAdded) {
- final int w = mWinFrame.width();
- final int h = mWinFrame.height();
- final int l = msg.arg1;
- final int t = msg.arg2;
- mWinFrame.left = l;
- mWinFrame.right = l + w;
- mWinFrame.top = t;
- mWinFrame.bottom = t + h;
-
- mPendingBackDropFrame.set(mWinFrame);
- maybeHandleWindowMove(mWinFrame);
- }
- break;
- case MSG_WINDOW_FOCUS_CHANGED: {
- if (mAdded) {
- boolean hasWindowFocus = msg.arg1 != 0;
- mAttachInfo.mHasWindowFocus = hasWindowFocus;
-
- profileRendering(hasWindowFocus);
-
- if (hasWindowFocus) {
- boolean inTouchMode = msg.arg2 != 0;
- ensureTouchModeLocally(inTouchMode);
-
- if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()){
- mFullRedrawNeeded = true;
- try {
- final WindowManager.LayoutParams lp = mWindowAttributes;
- final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
- mAttachInfo.mThreadedRenderer.initializeIfNeeded(
- mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
- } catch (OutOfResourcesException e) {
- Log.e(mTag, "OutOfResourcesException locking surface", e);
+ break;
+ case MSG_WINDOW_FOCUS_CHANGED: {
+ final boolean hasWindowFocus = msg.arg1 != 0;
+ if (mAdded) {
+ mAttachInfo.mHasWindowFocus = hasWindowFocus;
+
+ profileRendering(hasWindowFocus);
+
+ if (hasWindowFocus) {
+ boolean inTouchMode = msg.arg2 != 0;
+ ensureTouchModeLocally(inTouchMode);
+ if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) {
+ mFullRedrawNeeded = true;
try {
- if (!mWindowSession.outOfMemory(mWindow)) {
- Slog.w(mTag, "No processes killed for memory; killing self");
- Process.killProcess(Process.myPid());
+ final WindowManager.LayoutParams lp = mWindowAttributes;
+ final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
+ mAttachInfo.mThreadedRenderer.initializeIfNeeded(
+ mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
+ } catch (OutOfResourcesException e) {
+ Log.e(mTag, "OutOfResourcesException locking surface", e);
+ try {
+ if (!mWindowSession.outOfMemory(mWindow)) {
+ Slog.w(mTag, "No processes killed for memory;"
+ + " killing self");
+ Process.killProcess(Process.myPid());
+ }
+ } catch (RemoteException ex) {
}
- } catch (RemoteException ex) {
+ // Retry in a bit.
+ sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2),
+ 500);
+ return;
}
- // Retry in a bit.
- sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500);
- return;
}
}
- }
- mLastWasImTarget = WindowManager.LayoutParams
- .mayUseInputMethod(mWindowAttributes.flags);
-
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
- imm.onPreWindowFocus(mView, hasWindowFocus);
- }
- if (mView != null) {
- mAttachInfo.mKeyDispatchState.reset();
- mView.dispatchWindowFocusChanged(hasWindowFocus);
- mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
+ mLastWasImTarget = WindowManager.LayoutParams
+ .mayUseInputMethod(mWindowAttributes.flags);
- if (mAttachInfo.mTooltipHost != null) {
- mAttachInfo.mTooltipHost.hideTooltip();
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
+ imm.onPreWindowFocus(mView, hasWindowFocus);
}
- }
+ if (mView != null) {
+ mAttachInfo.mKeyDispatchState.reset();
+ mView.dispatchWindowFocusChanged(hasWindowFocus);
+ mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
- // Note: must be done after the focus change callbacks,
- // so all of the view state is set up correctly.
- if (hasWindowFocus) {
- if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
- imm.onPostWindowFocus(mView, mView.findFocus(),
- mWindowAttributes.softInputMode,
- !mHasHadWindowFocus, mWindowAttributes.flags);
+ if (mAttachInfo.mTooltipHost != null) {
+ mAttachInfo.mTooltipHost.hideTooltip();
+ }
}
- // Clear the forward bit. We can just do this directly, since
- // the window manager doesn't care about it.
- mWindowAttributes.softInputMode &=
- ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
- ((WindowManager.LayoutParams)mView.getLayoutParams())
- .softInputMode &=
+
+ // Note: must be done after the focus change callbacks,
+ // so all of the view state is set up correctly.
+ if (hasWindowFocus) {
+ if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
+ imm.onPostWindowFocus(mView, mView.findFocus(),
+ mWindowAttributes.softInputMode,
+ !mHasHadWindowFocus, mWindowAttributes.flags);
+ }
+ // Clear the forward bit. We can just do this directly, since
+ // the window manager doesn't care about it.
+ mWindowAttributes.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
- mHasHadWindowFocus = true;
- } else {
- if (mPointerCapture) {
- handlePointerCaptureChanged(false);
+ ((WindowManager.LayoutParams) mView.getLayoutParams())
+ .softInputMode &=
+ ~WindowManager.LayoutParams
+ .SOFT_INPUT_IS_FORWARD_NAVIGATION;
+ mHasHadWindowFocus = true;
+ } else {
+ if (mPointerCapture) {
+ handlePointerCaptureChanged(false);
+ }
}
}
- }
- } break;
- case MSG_DIE:
- doDie();
- break;
- case MSG_DISPATCH_INPUT_EVENT: {
- SomeArgs args = (SomeArgs)msg.obj;
- InputEvent event = (InputEvent)args.arg1;
- InputEventReceiver receiver = (InputEventReceiver)args.arg2;
- enqueueInputEvent(event, receiver, 0, true);
- args.recycle();
- } break;
- case MSG_SYNTHESIZE_INPUT_EVENT: {
- InputEvent event = (InputEvent)msg.obj;
- enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
- } break;
- case MSG_DISPATCH_KEY_FROM_IME: {
- if (LOCAL_LOGV) Log.v(
- TAG, "Dispatching key "
- + msg.obj + " from IME to " + mView);
- KeyEvent event = (KeyEvent)msg.obj;
- if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
- // The IME is trying to say this event is from the
- // system! Bad bad bad!
- //noinspection UnusedAssignment
- event = KeyEvent.changeFlags(event, event.getFlags() &
- ~KeyEvent.FLAG_FROM_SYSTEM);
- }
- enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
- } break;
- case MSG_CHECK_FOCUS: {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- imm.checkFocus();
- }
- } break;
- case MSG_CLOSE_SYSTEM_DIALOGS: {
- if (mView != null) {
- mView.onCloseSystemDialogs((String)msg.obj);
- }
- } break;
- case MSG_DISPATCH_DRAG_EVENT:
- case MSG_DISPATCH_DRAG_LOCATION_EVENT: {
- DragEvent event = (DragEvent)msg.obj;
- event.mLocalState = mLocalDragState; // only present when this app called startDrag()
- handleDragEvent(event);
- } break;
- case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
- handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
- } break;
- case MSG_UPDATE_CONFIGURATION: {
- Configuration config = (Configuration) msg.obj;
- if (config.isOtherSeqNewer(
- mLastReportedMergedConfiguration.getMergedConfiguration())) {
- // If we already have a newer merged config applied - use its global part.
- config = mLastReportedMergedConfiguration.getGlobalConfiguration();
- }
+ mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
+ } break;
+ case MSG_DIE:
+ doDie();
+ break;
+ case MSG_DISPATCH_INPUT_EVENT: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ InputEvent event = (InputEvent) args.arg1;
+ InputEventReceiver receiver = (InputEventReceiver) args.arg2;
+ enqueueInputEvent(event, receiver, 0, true);
+ args.recycle();
+ } break;
+ case MSG_SYNTHESIZE_INPUT_EVENT: {
+ InputEvent event = (InputEvent) msg.obj;
+ enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
+ } break;
+ case MSG_DISPATCH_KEY_FROM_IME: {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Dispatching key " + msg.obj + " from IME to " + mView);
+ }
+ KeyEvent event = (KeyEvent) msg.obj;
+ if ((event.getFlags() & KeyEvent.FLAG_FROM_SYSTEM) != 0) {
+ // The IME is trying to say this event is from the
+ // system! Bad bad bad!
+ //noinspection UnusedAssignment
+ event = KeyEvent.changeFlags(event,
+ event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
+ }
+ enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
+ } break;
+ case MSG_CHECK_FOCUS: {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.checkFocus();
+ }
+ } break;
+ case MSG_CLOSE_SYSTEM_DIALOGS: {
+ if (mView != null) {
+ mView.onCloseSystemDialogs((String) msg.obj);
+ }
+ } break;
+ case MSG_DISPATCH_DRAG_EVENT: {
+ } // fall through
+ case MSG_DISPATCH_DRAG_LOCATION_EVENT: {
+ DragEvent event = (DragEvent) msg.obj;
+ // only present when this app called startDrag()
+ event.mLocalState = mLocalDragState;
+ handleDragEvent(event);
+ } break;
+ case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
+ handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
+ } break;
+ case MSG_UPDATE_CONFIGURATION: {
+ Configuration config = (Configuration) msg.obj;
+ if (config.isOtherSeqNewer(
+ mLastReportedMergedConfiguration.getMergedConfiguration())) {
+ // If we already have a newer merged config applied - use its global part.
+ config = mLastReportedMergedConfiguration.getGlobalConfiguration();
+ }
- // Use the newer global config and last reported override config.
- mPendingMergedConfiguration.setConfiguration(config,
- mLastReportedMergedConfiguration.getOverrideConfiguration());
+ // Use the newer global config and last reported override config.
+ mPendingMergedConfiguration.setConfiguration(config,
+ mLastReportedMergedConfiguration.getOverrideConfiguration());
- performConfigurationChange(mPendingMergedConfiguration, false /* force */,
- INVALID_DISPLAY /* same display */);
- } break;
- case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
- setAccessibilityFocus(null, null);
- } break;
- case MSG_INVALIDATE_WORLD: {
- if (mView != null) {
- invalidateWorld(mView);
- }
- } break;
- case MSG_DISPATCH_WINDOW_SHOWN: {
- handleDispatchWindowShown();
- } break;
- case MSG_REQUEST_KEYBOARD_SHORTCUTS: {
- final IResultReceiver receiver = (IResultReceiver) msg.obj;
- final int deviceId = msg.arg1;
- handleRequestKeyboardShortcuts(receiver, deviceId);
- } break;
- case MSG_UPDATE_POINTER_ICON: {
- MotionEvent event = (MotionEvent) msg.obj;
- resetPointerIcon(event);
- } break;
- case MSG_POINTER_CAPTURE_CHANGED: {
- final boolean hasCapture = msg.arg1 != 0;
- handlePointerCaptureChanged(hasCapture);
- } break;
- case MSG_DRAW_FINISHED: {
- pendingDrawFinished();
- } break;
+ performConfigurationChange(mPendingMergedConfiguration, false /* force */,
+ INVALID_DISPLAY /* same display */);
+ } break;
+ case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
+ setAccessibilityFocus(null, null);
+ } break;
+ case MSG_INVALIDATE_WORLD: {
+ if (mView != null) {
+ invalidateWorld(mView);
+ }
+ } break;
+ case MSG_DISPATCH_WINDOW_SHOWN: {
+ handleDispatchWindowShown();
+ } break;
+ case MSG_REQUEST_KEYBOARD_SHORTCUTS: {
+ final IResultReceiver receiver = (IResultReceiver) msg.obj;
+ final int deviceId = msg.arg1;
+ handleRequestKeyboardShortcuts(receiver, deviceId);
+ } break;
+ case MSG_UPDATE_POINTER_ICON: {
+ MotionEvent event = (MotionEvent) msg.obj;
+ resetPointerIcon(event);
+ } break;
+ case MSG_POINTER_CAPTURE_CHANGED: {
+ final boolean hasCapture = msg.arg1 != 0;
+ handlePointerCaptureChanged(hasCapture);
+ } break;
+ case MSG_DRAW_FINISHED: {
+ pendingDrawFinished();
+ } break;
}
}
}
@@ -4203,6 +4208,18 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ protected void onWindowFocusChanged(boolean hasWindowFocus) {
+ if (mNext != null) {
+ mNext.onWindowFocusChanged(hasWindowFocus);
+ }
+ }
+
+ protected void onDetachedFromWindow() {
+ if (mNext != null) {
+ mNext.onDetachedFromWindow();
+ }
+ }
+
protected boolean shouldDropInputEvent(QueuedInputEvent q) {
if (mView == null || !mAdded) {
Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent);
@@ -4956,9 +4973,9 @@ public final class ViewRootImpl implements ViewParent,
final MotionEvent event = (MotionEvent)q.mEvent;
final int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
- mTrackball.cancel(event);
+ mTrackball.cancel();
} else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
- mJoystick.cancel(event);
+ mJoystick.cancel();
} else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
== InputDevice.SOURCE_TOUCH_NAVIGATION) {
mTouchNavigation.cancel(event);
@@ -4967,6 +4984,18 @@ public final class ViewRootImpl implements ViewParent,
}
super.onDeliverToNext(q);
}
+
+ @Override
+ protected void onWindowFocusChanged(boolean hasWindowFocus) {
+ if (!hasWindowFocus) {
+ mJoystick.cancel();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ mJoystick.cancel();
+ }
}
/**
@@ -5079,7 +5108,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
- public void cancel(MotionEvent event) {
+ public void cancel() {
mLastTime = Integer.MIN_VALUE;
// If we reach this, we consumed a trackball event.
@@ -5263,14 +5292,11 @@ public final class ViewRootImpl implements ViewParent,
* Creates dpad events from unhandled joystick movements.
*/
final class SyntheticJoystickHandler extends Handler {
- private final static String TAG = "SyntheticJoystickHandler";
private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 1;
private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 2;
- private int mLastXDirection;
- private int mLastYDirection;
- private int mLastXKeyCode;
- private int mLastYKeyCode;
+ private final JoystickAxesState mJoystickAxesState = new JoystickAxesState();
+ private final SparseArray<KeyEvent> mDeviceKeyEvents = new SparseArray<>();
public SyntheticJoystickHandler() {
super(true);
@@ -5281,11 +5307,10 @@ public final class ViewRootImpl implements ViewParent,
switch (msg.what) {
case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
- KeyEvent oldEvent = (KeyEvent)msg.obj;
- KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
- SystemClock.uptimeMillis(),
- oldEvent.getRepeatCount() + 1);
if (mAttachInfo.mHasWindowFocus) {
+ KeyEvent oldEvent = (KeyEvent) msg.obj;
+ KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
+ SystemClock.uptimeMillis(), oldEvent.getRepeatCount() + 1);
enqueueInputEvent(e);
Message m = obtainMessage(msg.what, e);
m.setAsynchronous(true);
@@ -5297,97 +5322,176 @@ public final class ViewRootImpl implements ViewParent,
public void process(MotionEvent event) {
switch(event.getActionMasked()) {
- case MotionEvent.ACTION_CANCEL:
- cancel(event);
- break;
- case MotionEvent.ACTION_MOVE:
- update(event, true);
- break;
- default:
- Log.w(mTag, "Unexpected action: " + event.getActionMasked());
+ case MotionEvent.ACTION_CANCEL:
+ cancel();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ update(event);
+ break;
+ default:
+ Log.w(mTag, "Unexpected action: " + event.getActionMasked());
}
}
- private void cancel(MotionEvent event) {
+ private void cancel() {
removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
- update(event, false);
- }
-
- private void update(MotionEvent event, boolean synthesizeNewKeys) {
+ for (int i = 0; i < mDeviceKeyEvents.size(); i++) {
+ final KeyEvent keyEvent = mDeviceKeyEvents.valueAt(i);
+ if (keyEvent != null) {
+ enqueueInputEvent(KeyEvent.changeTimeRepeat(keyEvent,
+ SystemClock.uptimeMillis(), 0));
+ }
+ }
+ mDeviceKeyEvents.clear();
+ mJoystickAxesState.resetState();
+ }
+
+ private void update(MotionEvent event) {
+ final int historySize = event.getHistorySize();
+ for (int h = 0; h < historySize; h++) {
+ final long time = event.getHistoricalEventTime(h);
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X,
+ event.getHistoricalAxisValue(MotionEvent.AXIS_X, 0, h));
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y,
+ event.getHistoricalAxisValue(MotionEvent.AXIS_Y, 0, h));
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X,
+ event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, 0, h));
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y,
+ event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, 0, h));
+ }
final long time = event.getEventTime();
- final int metaState = event.getMetaState();
- final int deviceId = event.getDeviceId();
- final int source = event.getSource();
-
- int xDirection = joystickAxisValueToDirection(
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X,
+ event.getAxisValue(MotionEvent.AXIS_X));
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y,
+ event.getAxisValue(MotionEvent.AXIS_Y));
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X,
event.getAxisValue(MotionEvent.AXIS_HAT_X));
- if (xDirection == 0) {
- xDirection = joystickAxisValueToDirection(event.getX());
- }
-
- int yDirection = joystickAxisValueToDirection(
+ mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y,
event.getAxisValue(MotionEvent.AXIS_HAT_Y));
- if (yDirection == 0) {
- yDirection = joystickAxisValueToDirection(event.getY());
- }
+ }
+
+ final class JoystickAxesState {
+ // State machine: from neutral state (no button press) can go into
+ // button STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state, emitting an ACTION_DOWN event.
+ // From STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state can go into neutral state,
+ // emitting an ACTION_UP event.
+ private static final int STATE_UP_OR_LEFT = -1;
+ private static final int STATE_NEUTRAL = 0;
+ private static final int STATE_DOWN_OR_RIGHT = 1;
+
+ final int[] mAxisStatesHat = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_HAT_X, AXIS_HAT_Y}
+ final int[] mAxisStatesStick = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_X, AXIS_Y}
+
+ void resetState() {
+ mAxisStatesHat[0] = STATE_NEUTRAL;
+ mAxisStatesHat[1] = STATE_NEUTRAL;
+ mAxisStatesStick[0] = STATE_NEUTRAL;
+ mAxisStatesStick[1] = STATE_NEUTRAL;
+ }
+
+ void updateStateForAxis(MotionEvent event, long time, int axis, float value) {
+ // Emit KeyEvent if necessary
+ // axis can be AXIS_X, AXIS_Y, AXIS_HAT_X, AXIS_HAT_Y
+ final int axisStateIndex;
+ final int repeatMessage;
+ if (isXAxis(axis)) {
+ axisStateIndex = 0;
+ repeatMessage = MSG_ENQUEUE_X_AXIS_KEY_REPEAT;
+ } else if (isYAxis(axis)) {
+ axisStateIndex = 1;
+ repeatMessage = MSG_ENQUEUE_Y_AXIS_KEY_REPEAT;
+ } else {
+ Log.e(mTag, "Unexpected axis " + axis + " in updateStateForAxis!");
+ return;
+ }
+ final int newState = joystickAxisValueToState(value);
+
+ final int currentState;
+ if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) {
+ currentState = mAxisStatesStick[axisStateIndex];
+ } else {
+ currentState = mAxisStatesHat[axisStateIndex];
+ }
- if (xDirection != mLastXDirection) {
- if (mLastXKeyCode != 0) {
- removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
- enqueueInputEvent(new KeyEvent(time, time,
- KeyEvent.ACTION_UP, mLastXKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
- mLastXKeyCode = 0;
+ if (currentState == newState) {
+ return;
}
- mLastXDirection = xDirection;
+ final int metaState = event.getMetaState();
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
- if (xDirection != 0 && synthesizeNewKeys) {
- mLastXKeyCode = xDirection > 0
- ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
- final KeyEvent e = new KeyEvent(time, time,
- KeyEvent.ACTION_DOWN, mLastXKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
- enqueueInputEvent(e);
- Message m = obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e);
- m.setAsynchronous(true);
- sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+ if (currentState == STATE_DOWN_OR_RIGHT || currentState == STATE_UP_OR_LEFT) {
+ // send a button release event
+ final int keyCode = joystickAxisAndStateToKeycode(axis, currentState);
+ if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+ enqueueInputEvent(new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode,
+ 0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+ // remove the corresponding pending UP event if focus lost/view detached
+ mDeviceKeyEvents.put(deviceId, null);
+ }
+ removeMessages(repeatMessage);
}
- }
- if (yDirection != mLastYDirection) {
- if (mLastYKeyCode != 0) {
- removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
- enqueueInputEvent(new KeyEvent(time, time,
- KeyEvent.ACTION_UP, mLastYKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
- mLastYKeyCode = 0;
+ if (newState == STATE_DOWN_OR_RIGHT || newState == STATE_UP_OR_LEFT) {
+ // send a button down event
+ final int keyCode = joystickAxisAndStateToKeycode(axis, newState);
+ if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+ KeyEvent keyEvent = new KeyEvent(time, time, KeyEvent.ACTION_DOWN, keyCode,
+ 0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
+ enqueueInputEvent(keyEvent);
+ Message m = obtainMessage(repeatMessage, keyEvent);
+ m.setAsynchronous(true);
+ sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+ // store the corresponding ACTION_UP event so that it can be sent
+ // if focus is lost or root view is removed
+ mDeviceKeyEvents.put(deviceId,
+ new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode,
+ 0, metaState, deviceId, 0,
+ KeyEvent.FLAG_FALLBACK | KeyEvent.FLAG_CANCELED,
+ source));
+ }
+ }
+ if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) {
+ mAxisStatesStick[axisStateIndex] = newState;
+ } else {
+ mAxisStatesHat[axisStateIndex] = newState;
}
+ }
- mLastYDirection = yDirection;
+ private boolean isXAxis(int axis) {
+ return axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_HAT_X;
+ }
+ private boolean isYAxis(int axis) {
+ return axis == MotionEvent.AXIS_Y || axis == MotionEvent.AXIS_HAT_Y;
+ }
- if (yDirection != 0 && synthesizeNewKeys) {
- mLastYKeyCode = yDirection > 0
- ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
- final KeyEvent e = new KeyEvent(time, time,
- KeyEvent.ACTION_DOWN, mLastYKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
- enqueueInputEvent(e);
- Message m = obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e);
- m.setAsynchronous(true);
- sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+ private int joystickAxisAndStateToKeycode(int axis, int state) {
+ if (isXAxis(axis) && state == STATE_UP_OR_LEFT) {
+ return KeyEvent.KEYCODE_DPAD_LEFT;
+ }
+ if (isXAxis(axis) && state == STATE_DOWN_OR_RIGHT) {
+ return KeyEvent.KEYCODE_DPAD_RIGHT;
+ }
+ if (isYAxis(axis) && state == STATE_UP_OR_LEFT) {
+ return KeyEvent.KEYCODE_DPAD_UP;
}
+ if (isYAxis(axis) && state == STATE_DOWN_OR_RIGHT) {
+ return KeyEvent.KEYCODE_DPAD_DOWN;
+ }
+ Log.e(mTag, "Unknown axis " + axis + " or direction " + state);
+ return KeyEvent.KEYCODE_UNKNOWN; // should never happen
}
- }
- private int joystickAxisValueToDirection(float value) {
- if (value >= 0.5f) {
- return 1;
- } else if (value <= -0.5f) {
- return -1;
- } else {
- return 0;
+ private int joystickAxisValueToState(float value) {
+ if (value >= 0.5f) {
+ return STATE_DOWN_OR_RIGHT;
+ } else if (value <= -0.5f) {
+ return STATE_UP_OR_LEFT;
+ } else {
+ return STATE_NEUTRAL;
+ }
}
}
}
@@ -6108,7 +6212,6 @@ public final class ViewRootImpl implements ViewParent,
if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);
}
- //Log.d(mTag, ">>>>>> CALLING relayout");
if (params != null && mOrigWindowType != params.type) {
// For compatibility with old apps, don't crash here.
if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
@@ -6129,7 +6232,6 @@ public final class ViewRootImpl implements ViewParent,
mPendingAlwaysConsumeNavBar =
(relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0;
- //Log.d(mTag, "<<<<<< BACK FROM relayout");
if (restore) {
params.restore();
}
diff --git a/android/view/ViewStructure.java b/android/view/ViewStructure.java
index f671c349..309366c6 100644
--- a/android/view/ViewStructure.java
+++ b/android/view/ViewStructure.java
@@ -365,6 +365,30 @@ public abstract class ViewStructure {
public abstract void setDataIsSensitive(boolean sensitive);
/**
+ * Sets the minimum width in ems of the text associated with this view, when supported.
+ *
+ * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+ * when used for Assist.
+ */
+ public abstract void setMinTextEms(int minEms);
+
+ /**
+ * Sets the maximum width in ems of the text associated with this view, when supported.
+ *
+ * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+ * when used for Assist.
+ */
+ public abstract void setMaxTextEms(int maxEms);
+
+ /**
+ * Sets the maximum length of the text associated with this view, when supported.
+ *
+ * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+ * when used for Assist.
+ */
+ public abstract void setMaxTextLength(int maxLength);
+
+ /**
* Call when done populating a {@link ViewStructure} returned by
* {@link #asyncNewChild}.
*/
diff --git a/android/view/WindowManagerInternal.java b/android/view/WindowManagerInternal.java
index 98f8dc8e..69cc1002 100644
--- a/android/view/WindowManagerInternal.java
+++ b/android/view/WindowManagerInternal.java
@@ -335,8 +335,8 @@ public abstract class WindowManagerInternal {
public abstract void setOnHardKeyboardStatusChangeListener(
OnHardKeyboardStatusChangeListener listener);
- /** Returns true if the stack with the input Id is currently visible. */
- public abstract boolean isStackVisible(int stackId);
+ /** Returns true if a stack in the windowing mode is currently visible. */
+ public abstract boolean isStackVisible(int windowingMode);
/**
* @return True if and only if the docked divider is currently in resize mode.
diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java
index da72535d..137e551d 100644
--- a/android/view/WindowManagerPolicy.java
+++ b/android/view/WindowManagerPolicy.java
@@ -467,11 +467,8 @@ public interface WindowManagerPolicy {
*/
public boolean isDimming();
- /**
- * @return the stack id this windows belongs to, or {@link StackId#INVALID_STACK_ID} if
- * not attached to any stack.
- */
- int getStackId();
+ /** @return the current windowing mode of this window. */
+ int getWindowingMode();
/**
* Returns true if the window is current in multi-windowing mode. i.e. it shares the
diff --git a/android/view/accessibility/AccessibilityCache.java b/android/view/accessibility/AccessibilityCache.java
index 0f21c5c8..d7851171 100644
--- a/android/view/accessibility/AccessibilityCache.java
+++ b/android/view/accessibility/AccessibilityCache.java
@@ -329,8 +329,6 @@ public final class AccessibilityCache {
final long oldParentId = oldInfo.getParentNodeId();
if (info.getParentNodeId() != oldParentId) {
clearSubTreeLocked(windowId, oldParentId);
- } else {
- oldInfo.recycle();
}
}
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 0b9bc576..11cb046a 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -16,152 +16,46 @@
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,
- * and provides facilities for querying the accessibility state of the system.
- * Accessibility events are generated when something notable happens in the user interface,
+ * 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,
* 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
- * {@link android.accessibilityservice.AccessibilityService}.
+ * {@code android.accessibilityservice.AccessibilityService}.
*
* @see AccessibilityEvent
- * @see AccessibilityNodeInfo
- * @see android.accessibilityservice.AccessibilityService
- * @see Context#getSystemService
- * @see Context#ACCESSIBILITY_SERVICE
+ * @see android.content.Context#getSystemService
*/
-@SystemService(Context.ACCESSIBILITY_SERVICE)
+@SuppressWarnings("UnusedDeclaration")
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;
- int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+ private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
- 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 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}.
+ * Listener for the accessibility state.
*/
public interface AccessibilityStateChangeListener {
/**
- * Called when the accessibility enabled state changes.
+ * Called back on change in the accessibility state.
*
* @param enabled Whether accessibility is enabled.
*/
- void onAccessibilityStateChanged(boolean enabled);
+ public void onAccessibilityStateChanged(boolean enabled);
}
/**
@@ -177,24 +71,7 @@ public final class AccessibilityManager {
*
* @param enabled Whether touch exploration is 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);
+ public void onTouchExplorationStateChanged(boolean enabled);
}
/**
@@ -202,8 +79,6 @@ 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 {
@@ -212,72 +87,26 @@ public final class AccessibilityManager {
*
* @param enabled Whether high text contrast is enabled.
*/
- void onHighTextContrastStateChanged(boolean enabled);
+ public void onHighTextContrastStateChanged(boolean enabled);
}
private final IAccessibilityManagerClient.Stub mClient =
new IAccessibilityManagerClient.Stub() {
- @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();
- }
-
- @Override
- public void notifyServicesStateChanged() {
- final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
- synchronized (mLock) {
- if (mServicesStateChangeListeners.isEmpty()) {
- return;
+ public void setState(int state) {
}
- listeners = new ArrayMap<>(mServicesStateChangeListeners);
- }
- 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));
- }
- }
+ public void notifyServicesStateChanged() {
+ }
- @Override
- public void setRelevantEventTypes(int eventTypes) {
- mRelevantEventTypes = eventTypes;
- }
- };
+ public void setRelevantEventTypes(int 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;
}
@@ -285,68 +114,21 @@ 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;
}
/**
- * @hide
- */
- @VisibleForTesting
- public Handler.Callback getCallback() {
- return mCallback;
- }
-
- /**
- * Returns if the accessibility in the system is enabled.
+ * Returns if the {@link AccessibilityManager} is enabled.
*
- * @return True if accessibility is enabled, false otherwise.
+ * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
*/
public boolean isEnabled() {
- synchronized (mLock) {
- IAccessibilityManager service = getServiceLocked();
- if (service == null) {
- return false;
- }
- return mIsEnabled;
- }
+ return false;
}
/**
@@ -355,13 +137,7 @@ public final class AccessibilityManager {
* @return True if touch exploration is enabled, false otherwise.
*/
public boolean isTouchExplorationEnabled() {
- synchronized (mLock) {
- IAccessibilityManager service = getServiceLocked();
- if (service == null) {
- return false;
- }
- return mIsTouchExplorationEnabled;
- }
+ return true;
}
/**
@@ -371,169 +147,35 @@ 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() {
- synchronized (mLock) {
- IAccessibilityManager service = getServiceLocked();
- if (service == null) {
- return false;
- }
- return mIsHighTextContrastEnabled;
- }
+ return false;
}
/**
* 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 feedback interruption from all accessibility services.
+ * Requests interruption of the accessibility feedback 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() {
- 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);
+ return Collections.emptyList();
}
- /**
- * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
- *
- * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
- */
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
- 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();
- }
+ return Collections.emptyList();
}
/**
@@ -548,48 +190,21 @@ 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) {
- 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();
- }
+ return Collections.emptyList();
}
/**
* Registers an {@link AccessibilityStateChangeListener} for changes in
- * the global accessibility state of the system. Equivalent to calling
- * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
- * with a null handler.
+ * the global accessibility state of the system.
*
* @param listener The listener.
- * @return Always returns {@code true}.
+ * @return True if successfully registered.
*/
public boolean addAccessibilityStateChangeListener(
- @NonNull AccessibilityStateChangeListener listener) {
- addAccessibilityStateChangeListener(listener, null);
+ AccessibilityStateChangeListener listener) {
return true;
}
@@ -603,40 +218,22 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
*/
public void addAccessibilityStateChangeListener(
- @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
- synchronized (mLock) {
- mAccessibilityStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
- }
- }
+ @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {}
- /**
- * Unregisters an {@link AccessibilityStateChangeListener}.
- *
- * @param listener The listener.
- * @return True if the listener was previously registered.
- */
public boolean removeAccessibilityStateChangeListener(
- @NonNull AccessibilityStateChangeListener listener) {
- synchronized (mLock) {
- int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
- mAccessibilityStateChangeListeners.remove(listener);
- return (index >= 0);
- }
+ AccessibilityStateChangeListener listener) {
+ return true;
}
/**
* Registers a {@link TouchExplorationStateChangeListener} for changes in
- * the global touch exploration state of the system. Equivalent to calling
- * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
- * with a null handler.
+ * the global touch exploration state of the system.
*
* @param listener The listener.
- * @return Always returns {@code true}.
+ * @return True if successfully registered.
*/
public boolean addTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
- addTouchExplorationStateChangeListener(listener, null);
return true;
}
@@ -650,103 +247,17 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
*/
public void addTouchExplorationStateChangeListener(
- @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
- synchronized (mLock) {
- mTouchExplorationStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
- }
- }
+ @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {}
/**
* Unregisters a {@link TouchExplorationStateChangeListener}.
*
* @param listener The listener.
- * @return True if listener was previously registered.
+ * @return True if successfully unregistered.
*/
public boolean removeTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
- 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);
+ return true;
}
/**
@@ -758,12 +269,7 @@ public final class AccessibilityManager {
* @hide
*/
public void addHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
- synchronized (mLock) {
- mHighTextContrastStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
- }
- }
+ @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {}
/**
* Unregisters a {@link HighTextContrastChangeListener}.
@@ -773,51 +279,7 @@ public final class AccessibilityManager {
* @hide
*/
public void removeHighTextContrastStateChangeListener(
- @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;
- }
- }
+ @NonNull HighTextContrastChangeListener listener) {}
/**
* Sets the current state and notifies listeners, if necessary.
@@ -825,314 +287,14 @@ 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 4fb2a99a..e564fa34 100644
--- a/android/view/autofill/AutofillManager.java
+++ b/android/view/autofill/AutofillManager.java
@@ -91,10 +91,10 @@ import java.util.Objects;
* </ul>
*
* <p>When the service returns datasets, the Android System displays an autofill dataset picker
- * UI affordance associated with the view, when the view is focused on and is part of a dataset.
- * The application can be notified when the affordance is shown by registering an
+ * UI associated with the view, when the view is focused on and is part of a dataset.
+ * The application can be notified when the UI is shown by registering an
* {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user
- * selects a dataset from the affordance, all views present in the dataset are autofilled, through
+ * selects a dataset from the UI, all views present in the dataset are autofilled, through
* calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}.
*
* <p>When the service returns ids of savable views, the Android System keeps track of changes
@@ -108,7 +108,7 @@ import java.util.Objects;
* </ul>
*
* <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System
- * shows a save UI affordance if the value of savable views have changed. If the user selects the
+ * shows an autofill save UI if the value of savable views have changed. If the user selects the
* option to Save, the current value of the views is then sent to the autofill service.
*
* <p>It is safe to call into its methods from any thread.
@@ -150,6 +150,12 @@ public final class AutofillManager {
* service authentication will contain the Bundle set by
* {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
*
+ * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service
+ * can also add this bundle to the {@link Intent} set as the
+ * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request,
+ * so the bundle can be recovered later on
+ * {@link android.service.autofill.SaveRequest#getClientState()}.
+ *
* <p>
* Type: {@link android.os.Bundle}
*/
@@ -311,6 +317,14 @@ public final class AutofillManager {
@GuardedBy("mLock")
@Nullable private ArraySet<AutofillId> mFillableIds;
+ /** If set, session is commited when the field is clicked. */
+ @GuardedBy("mLock")
+ @Nullable private AutofillId mSaveTriggerId;
+
+ /** If set, session is commited when the activity is finished; otherwise session is canceled. */
+ @GuardedBy("mLock")
+ private boolean mSaveOnFinish;
+
/** @hide */
public interface AutofillClient {
/**
@@ -834,6 +848,46 @@ public final class AutofillManager {
}
}
+
+ /**
+ * Called when a {@link View} is clicked. Currently only used by views that should trigger save.
+ *
+ * @hide
+ */
+ public void notifyViewClicked(View view) {
+ final AutofillId id = view.getAutofillId();
+
+ if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId);
+
+ synchronized (mLock) {
+ if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) {
+ if (sDebug) Log.d(TAG, "triggering commit by click of " + id);
+ commitLocked();
+ mMetricsLogger.action(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED,
+ mContext.getPackageName());
+ }
+ }
+ }
+
+ /**
+ * Called by {@link android.app.Activity} to commit or cancel the session on finish.
+ *
+ * @hide
+ */
+ public void onActivityFinished() {
+ if (!hasAutofillFeature()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mSaveOnFinish) {
+ commitLocked();
+ } else {
+ if (sDebug) Log.d(TAG, "Cancelling session on finish() as requested by service");
+ cancelLocked();
+ }
+ }
+ }
+
/**
* Called to indicate the current autofill context should be commited.
*
@@ -850,12 +904,15 @@ public final class AutofillManager {
return;
}
synchronized (mLock) {
- if (!mEnabled && !isActiveLocked()) {
- return;
- }
+ commitLocked();
+ }
+ }
- finishSessionLocked();
+ private void commitLocked() {
+ if (!mEnabled && !isActiveLocked()) {
+ return;
}
+ finishSessionLocked();
}
/**
@@ -874,12 +931,15 @@ public final class AutofillManager {
return;
}
synchronized (mLock) {
- if (!mEnabled && !isActiveLocked()) {
- return;
- }
+ cancelLocked();
+ }
+ }
- cancelSessionLocked();
+ private void cancelLocked() {
+ if (!mEnabled && !isActiveLocked()) {
+ return;
}
+ cancelSessionLocked();
}
/** @hide */
@@ -937,7 +997,12 @@ public final class AutofillManager {
}
private AutofillClient getClientLocked() {
- return mContext.getAutofillClient();
+ final AutofillClient client = mContext.getAutofillClient();
+ if (client == null && sDebug) {
+ Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context "
+ + mContext);
+ }
+ return client;
}
/** @hide */
@@ -959,6 +1024,10 @@ public final class AutofillManager {
final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
final Bundle responseData = new Bundle();
responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
+ final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE);
+ if (newClientState != null) {
+ responseData.putBundle(EXTRA_CLIENT_STATE, newClientState);
+ }
try {
mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
mContext.getUserId());
@@ -1038,6 +1107,7 @@ public final class AutofillManager {
mState = STATE_UNKNOWN;
mTrackedViews = null;
mFillableIds = null;
+ mSaveTriggerId = null;
}
private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
@@ -1289,12 +1359,15 @@ public final class AutofillManager {
/**
* Set the tracked views.
*
- * @param trackedIds The views to be tracked
+ * @param trackedIds The views to be tracked.
* @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
+ * @param saveOnFinish Finish the session once the activity is finished.
* @param fillableIds Views that might anchor FillUI.
+ * @param saveTriggerId View that when clicked triggers commit().
*/
private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
- boolean saveOnAllViewsInvisible, @Nullable AutofillId[] fillableIds) {
+ boolean saveOnAllViewsInvisible, boolean saveOnFinish,
+ @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) {
synchronized (mLock) {
if (mEnabled && mSessionId == sessionId) {
if (saveOnAllViewsInvisible) {
@@ -1302,6 +1375,7 @@ public final class AutofillManager {
} else {
mTrackedViews = null;
}
+ mSaveOnFinish = saveOnFinish;
if (fillableIds != null) {
if (mFillableIds == null) {
mFillableIds = new ArraySet<>(fillableIds.length);
@@ -1314,10 +1388,30 @@ public final class AutofillManager {
+ ", mFillableIds" + mFillableIds);
}
}
+
+ if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) {
+ // Turn off trigger on previous view id.
+ setNotifyOnClickLocked(mSaveTriggerId, false);
+ }
+
+ if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) {
+ // Turn on trigger on new view id.
+ mSaveTriggerId = saveTriggerId;
+ setNotifyOnClickLocked(mSaveTriggerId, true);
+ }
}
}
}
+ private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) {
+ final View view = findView(id);
+ if (view == null) {
+ Log.w(TAG, "setNotifyOnClick(): invalid id: " + id);
+ return;
+ }
+ view.setNotifyAutofillManagerOnClick(notify);
+ }
+
private void setSaveUiState(int sessionId, boolean shown) {
if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown);
synchronized (mLock) {
@@ -1490,6 +1584,7 @@ public final class AutofillManager {
final String pfx = outerPrefix + " ";
pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
+ pw.print(pfx); pw.print("context: "); pw.println(mContext);
pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
@@ -1504,6 +1599,8 @@ public final class AutofillManager {
pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds);
}
pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
+ pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
+ pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
}
private String getStateAsStringLocked() {
@@ -1752,7 +1849,7 @@ public final class AutofillManager {
* Callback for autofill related events.
*
* <p>Typically used for applications that display their own "auto-complete" views, so they can
- * enable / disable such views when the autofill UI affordance is shown / hidden.
+ * enable / disable such views when the autofill UI is shown / hidden.
*/
public abstract static class AutofillCallback {
@@ -1762,26 +1859,26 @@ public final class AutofillManager {
public @interface AutofillEventType {}
/**
- * The autofill input UI affordance associated with the view was shown.
+ * The autofill input UI associated with the view was shown.
*
- * <p>If the view provides its own auto-complete UI affordance and its currently shown, it
+ * <p>If the view provides its own auto-complete UI and its currently shown, it
* should be hidden upon receiving this event.
*/
public static final int EVENT_INPUT_SHOWN = 1;
/**
- * The autofill input UI affordance associated with the view was hidden.
+ * The autofill input UI associated with the view was hidden.
*
- * <p>If the view provides its own auto-complete UI affordance that was hidden upon a
+ * <p>If the view provides its own auto-complete UI that was hidden upon a
* {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
*/
public static final int EVENT_INPUT_HIDDEN = 2;
/**
- * The autofill input UI affordance associated with the view isn't shown because
+ * The autofill input UI associated with the view isn't shown because
* autofill is not available.
*
- * <p>If the view provides its own auto-complete UI affordance but was not displaying it
+ * <p>If the view provides its own auto-complete UI but was not displaying it
* to avoid flickering, it could shown it upon receiving this event.
*/
public static final int EVENT_INPUT_UNAVAILABLE = 3;
@@ -1883,12 +1980,12 @@ public final class AutofillManager {
@Override
public void setTrackedViews(int sessionId, AutofillId[] ids,
- boolean saveOnAllViewsInvisible, AutofillId[] fillableIds) {
+ boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds,
+ AutofillId saveTriggerId) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() ->
- afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, fillableIds)
- );
+ afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible,
+ saveOnFinish, fillableIds, saveTriggerId));
}
}
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
index bb1e693f..c3601d9d 100644
--- a/android/view/textclassifier/TextClassifier.java
+++ b/android/view/textclassifier/TextClassifier.java
@@ -152,4 +152,12 @@ public interface TextClassifier {
*/
@WorkerThread
default void logEvent(String source, String event) {}
+
+ /**
+ * Returns this TextClassifier's settings.
+ * @hide
+ */
+ default TextClassifierConstants getSettings() {
+ return TextClassifierConstants.DEFAULT;
+ }
}
diff --git a/android/view/textclassifier/TextClassifierConstants.java b/android/view/textclassifier/TextClassifierConstants.java
new file mode 100644
index 00000000..51e6168e
--- /dev/null
+++ b/android/view/textclassifier/TextClassifierConstants.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.Nullable;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+
+/**
+ * TextClassifier specific settings.
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * <pre>
+ * smart_selection_dark_launch (boolean)
+ * smart_selection_enabled_for_edit_text (boolean)
+ * </pre>
+ *
+ * <p>
+ * Type: string
+ * see also android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS
+ *
+ * Example of setting the values for testing.
+ * adb shell settings put global text_classifier_constants smart_selection_dark_launch=true,smart_selection_enabled_for_edit_text=true
+ * @hide
+ */
+public final class TextClassifierConstants {
+
+ private static final String LOG_TAG = "TextClassifierConstants";
+
+ private static final String SMART_SELECTION_DARK_LAUNCH =
+ "smart_selection_dark_launch";
+ private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT =
+ "smart_selection_enabled_for_edit_text";
+
+ private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false;
+ private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true;
+
+ /** Default settings. */
+ static final TextClassifierConstants DEFAULT = new TextClassifierConstants();
+
+ private final boolean mDarkLaunch;
+ private final boolean mSuggestSelectionEnabledForEditableText;
+
+ private TextClassifierConstants() {
+ mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT;
+ mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT;
+ }
+
+ private TextClassifierConstants(@Nullable String settings) {
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ try {
+ parser.setString(settings);
+ } catch (IllegalArgumentException e) {
+ // Failed to parse the settings string, log this and move on with defaults.
+ Slog.e(LOG_TAG, "Bad TextClassifier settings: " + settings);
+ }
+ mDarkLaunch = parser.getBoolean(
+ SMART_SELECTION_DARK_LAUNCH,
+ SMART_SELECTION_DARK_LAUNCH_DEFAULT);
+ mSuggestSelectionEnabledForEditableText = parser.getBoolean(
+ SMART_SELECTION_ENABLED_FOR_EDIT_TEXT,
+ SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT);
+ }
+
+ static TextClassifierConstants loadFromString(String settings) {
+ return new TextClassifierConstants(settings);
+ }
+
+ public boolean isDarkLaunch() {
+ return mDarkLaunch;
+ }
+
+ public boolean isSuggestSelectionEnabledForEditableText() {
+ return mSuggestSelectionEnabledForEditableText;
+ }
+}
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
index 2aa81a2c..ef087472 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -24,12 +24,12 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
-import android.icu.text.BreakIterator;
import android.net.Uri;
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.provider.Browser;
import android.provider.ContactsContract;
+import android.provider.Settings;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.method.WordIterator;
@@ -47,6 +47,7 @@ import com.android.internal.util.Preconditions;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -91,6 +92,8 @@ final class TextClassifierImpl implements TextClassifier {
@GuardedBy("mSmartSelectionLock") // Do not access outside this lock.
private SmartSelection mSmartSelection;
+ private TextClassifierConstants mSettings;
+
TextClassifierImpl(Context context) {
mContext = Preconditions.checkNotNull(context);
}
@@ -189,6 +192,15 @@ final class TextClassifierImpl implements TextClassifier {
}
}
+ @Override
+ public TextClassifierConstants getSettings() {
+ if (mSettings == null) {
+ mSettings = TextClassifierConstants.loadFromString(Settings.Global.getString(
+ mContext.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
+ }
+ return mSettings;
+ }
+
private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException {
synchronized (mSmartSelectionLock) {
localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList;
diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java
index f368c74a..8e1f2183 100644
--- a/android/view/textservice/TextServicesManager.java
+++ b/android/view/textservice/TextServicesManager.java
@@ -1,213 +1,58 @@
/*
- * Copyright (C) 2011 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. 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;
/**
- * 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>
- *
+ * A stub class of TextServicesManager for Layout-Lib.
*/
-@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
public final class TextServicesManager {
- 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));
- }
+ private static final TextServicesManager sInstance = new TextServicesManager();
+ private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
/**
* Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
* @hide
*/
public static TextServicesManager getInstance() {
- 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);
- }
+ return sInstance;
}
- /**
- * 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) {
- 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;
+ return null;
}
/**
* @hide
*/
public SpellCheckerInfo[] getEnabledSpellCheckers() {
- 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();
- }
+ return EMPTY_SPELL_CHECKER_INFO;
}
/**
* @hide
*/
public SpellCheckerInfo getCurrentSpellChecker() {
- try {
- // Passing null as a locale for ICS
- return mService.getCurrentSpellChecker(null);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return null;
}
/**
@@ -215,22 +60,13 @@ public final class TextServicesManager {
*/
public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
boolean allowImplicitlySelectedSubtype) {
- 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();
- }
+ return null;
}
/**
* @hide
*/
public boolean isSpellCheckerEnabled() {
- try {
- return mService.isSpellCheckerEnabled();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return false;
}
}
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index dfc81b2b..202f2046 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,3001 +16,223 @@
package android.webkit;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.Widget;
+import com.android.layoutlib.bridge.MockView;
+
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;
/**
- * <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>
+ * 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.
*
*/
-// 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;
+public class WebView extends MockView {
- /**
- * 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:";
/**
- * 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
+ * Construct a new WebView with a Context object.
+ * @param context A Context object used to access application assets.
*/
public WebView(Context context) {
this(context, null);
}
/**
- * 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
+ * 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.
*/
public WebView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.webViewStyle);
}
/**
- * 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.
+ * 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.
*/
- public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
+ public WebView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
}
-
- /**
- * 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
+
+ // START FAKE PUBLIC METHODS
+
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() {
- // The old implementation defaulted to true, so return true for consistency
- return true;
+ return false;
}
- /**
- * 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) {
- checkThread();
- return mProvider.getHttpAuthUsernamePassword(host, realm);
+ return null;
}
- /**
- * 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);
- }
}
- /**
- * 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 loadData(String data, String mimeType, String encoding) {
}
- /**
- * 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);
+ public void loadDataWithBaseURL(String baseUrl, String data,
+ String mimeType, String encoding, String failUrl) {
}
- /**
- * 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() {
- checkThread();
- return mProvider.canGoBack();
+ return false;
}
- /**
- * 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() {
- checkThread();
- return mProvider.canGoForward();
+ return false;
}
- /**
- * 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) {
- checkThread();
- return mProvider.canGoBackOrForward(steps);
+ return false;
}
- /**
- * 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) {
- checkThread();
- return mProvider.pageUp(top);
+ return false;
}
-
- /**
- * 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) {
- 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);
+ return false;
}
- /**
- * 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() {
- 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");
+ return null;
}
- /**
- * 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() {
- checkThread();
- return mProvider.getScale();
+ return 0;
}
- /**
- * 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();
}
- /**
- * 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);
+ public void requestFocusNodeHref(Message 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() {
- checkThread();
- return mProvider.getUrl();
+ return null;
}
- /**
- * 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() {
- checkThread();
- return mProvider.getTitle();
+ return null;
}
- /**
- * 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() {
- checkThread();
- return mProvider.getFavicon();
+ return null;
}
- /**
- * 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() {
- checkThread();
- return mProvider.getProgress();
+ return 0;
}
-
- /**
- * Gets the height of the HTML content.
- *
- * @return the height of the HTML content
- */
- @ViewDebug.ExportedProperty(category = "webview")
+
public int getContentHeight() {
- 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();
+ return 0;
}
- /**
- * 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();
}
- /**
- * 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);
+ public void clearCache() {
}
- /**
- * 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) {
- // 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();
+ return null;
}
- /**
- * 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);
}
- /**
- * 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 addJavascriptInterface(Object obj, String interfaceName) {
}
-
- 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() {
- checkThread();
- return mProvider.getZoomControls();
+ return null;
}
- /**
- * 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() {
- checkThread();
- return mProvider.zoomIn();
+ return false;
}
- /**
- * Performs zoom out in this WebView.
- *
- * @return {@code true} if zoom out succeeds, {@code false} if no zoom changes
- */
public boolean zoomOut() {
- 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());
+ return false;
}
}
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index afd11881..d4be7e57 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -165,7 +165,7 @@ 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;
+ private static final float MAGNIFIER_ZOOM = 1.25f;
@IntDef({MagnifierHandleTrigger.SELECTION_START,
MagnifierHandleTrigger.SELECTION_END,
MagnifierHandleTrigger.INSERTION})
@@ -476,6 +476,17 @@ public class Editor {
stopTextActionModeWithPreservingSelection();
}
+ void invalidateMagnifier() {
+ final DisplayMetrics dm = mTextView.getResources().getDisplayMetrics();
+ invalidateMagnifier(0, 0, dm.widthPixels, dm.heightPixels);
+ }
+
+ void invalidateMagnifier(final float l, final float t, final float r, final float b) {
+ if (mMagnifier != null) {
+ mTextView.post(() -> mMagnifier.invalidate(new RectF(l, t, r, b)));
+ }
+ }
+
private void discardTextDisplayLists() {
if (mTextRenderNodes != null) {
for (int i = 0; i < mTextRenderNodes.length; i++) {
@@ -4545,17 +4556,17 @@ public class Editor {
+ 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];
+ final float centerXOnScreen = mTextView.convertViewToScreenCoord(xPosInView, true);
+ final float centerYOnScreen = mTextView.convertViewToScreenCoord(yPosInView, false);
+ suspendBlink();
mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM);
}
protected final void dismissMagnifier() {
if (mMagnifier != null) {
mMagnifier.dismiss();
+ resumeBlink();
}
}
diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java
index 1b26f8e2..199b596a 100644
--- a/android/widget/RemoteViews.java
+++ b/android/widget/RemoteViews.java
@@ -2653,7 +2653,11 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Equivalent to calling
* {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
- * to launch the provided {@link PendingIntent}.
+ * to launch the provided {@link PendingIntent}. The source bounds
+ * ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the clicked
+ * view in screen space.
+ * Note that any activity options associated with the pendingIntent may get overridden
+ * before starting the intent.
*
* When setting the on-click action of items within collections (eg. {@link ListView},
* {@link StackView} etc.), this method will not work. Instead, use {@link
diff --git a/android/widget/SelectionActionModeHelper.java b/android/widget/SelectionActionModeHelper.java
index 3be42a5b..5e22650a 100644
--- a/android/widget/SelectionActionModeHelper.java
+++ b/android/widget/SelectionActionModeHelper.java
@@ -95,11 +95,15 @@ public final class SelectionActionModeHelper {
}
public void startActionModeAsync(boolean adjustSelection) {
+ // Check if the smart selection should run for editable text.
+ adjustSelection &= !mTextView.isTextEditable()
+ || mTextView.getTextClassifier().getSettings()
+ .isSuggestSelectionEnabledForEditableText();
+
mSelectionTracker.onOriginalSelection(
getText(mTextView),
mTextView.getSelectionStart(),
- mTextView.getSelectionEnd(),
- mTextView.isTextEditable());
+ mTextView.getSelectionEnd());
cancelAsyncTask();
if (skipTextClassification()) {
startActionMode(null);
@@ -196,7 +200,10 @@ public final class SelectionActionModeHelper {
private void startActionMode(@Nullable SelectionResult result) {
final CharSequence text = getText(mTextView);
if (result != null && text instanceof Spannable) {
- Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
+ // Do not change the selection if TextClassifier should be dark launched.
+ if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) {
+ Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
+ }
mTextClassification = result.mClassification;
} else {
mTextClassification = null;
@@ -377,7 +384,7 @@ public final class SelectionActionModeHelper {
}
private void resetTextClassificationHelper() {
- mTextClassificationHelper.reset(
+ mTextClassificationHelper.init(
mTextView.getTextClassifier(),
getText(mTextView),
mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
@@ -415,8 +422,7 @@ public final class SelectionActionModeHelper {
/**
* Called when the original selection happens, before smart selection is triggered.
*/
- public void onOriginalSelection(
- CharSequence text, int selectionStart, int selectionEnd, boolean editableText) {
+ public void onOriginalSelection(CharSequence text, int selectionStart, int selectionEnd) {
// 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();
@@ -812,11 +818,11 @@ public final class SelectionActionModeHelper {
TextClassificationHelper(TextClassifier textClassifier,
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
- reset(textClassifier, text, selectionStart, selectionEnd, locales);
+ init(textClassifier, text, selectionStart, selectionEnd, locales);
}
@UiThread
- public void reset(TextClassifier textClassifier,
+ public void init(TextClassifier textClassifier,
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
mTextClassifier = Preconditions.checkNotNull(textClassifier);
mText = Preconditions.checkNotNull(text).toString();
@@ -839,8 +845,12 @@ public final class SelectionActionModeHelper {
trimText();
final TextSelection selection = mTextClassifier.suggestSelection(
mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
- mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart);
- mSelectionEnd = Math.min(mText.length(), selection.getSelectionEndIndex() + mTrimStart);
+ // Do not classify new selection boundaries if TextClassifier should be dark launched.
+ if (!mTextClassifier.getSettings().isDarkLaunch()) {
+ mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart);
+ mSelectionEnd = Math.min(
+ mText.length(), selection.getSelectionEndIndex() + mTrimStart);
+ }
return performClassification(selection);
}
diff --git a/android/widget/TextView.java b/android/widget/TextView.java
index 24ae03c3..ce805526 100644
--- a/android/widget/TextView.java
+++ b/android/widget/TextView.java
@@ -9219,6 +9219,36 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ if (mEditor != null) {
+ mEditor.invalidateMagnifier();
+ }
+ }
+
+ @Override
+ public void invalidate(int l, int t, int r, int b) {
+ super.invalidate(l, t, r, b);
+
+ if (mEditor != null) {
+ mEditor.invalidateMagnifier(
+ convertViewToScreenCoord(l, true /* isHorizontal */),
+ convertViewToScreenCoord(t, false /* isHorizontal */),
+ convertViewToScreenCoord(r, true /* isHorizontal */),
+ convertViewToScreenCoord(b, false /* isHorizontal */));
+ }
+ }
+
+ float convertViewToScreenCoord(float viewCoord, boolean isHorizontal) {
+ final int[] coordinatesOnScreen = new int[2];
+ getLocationOnScreen(coordinatesOnScreen);
+ return isHorizontal
+ ? viewCoord + getTotalPaddingLeft() - getScrollX() + coordinatesOnScreen[0]
+ : viewCoord + getTotalPaddingTop() - getScrollY() + coordinatesOnScreen[1];
+ }
+
/**
* @return whether or not the cursor is visible (assuming this TextView is editable)
*
@@ -10338,6 +10368,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
structure.setTextStyle(getTextSize(), getCurrentTextColor(),
AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
+ } else {
+ structure.setMinTextEms(getMinEms());
+ structure.setMaxTextEms(getMaxEms());
+ int maxLength = -1;
+ for (InputFilter filter: getFilters()) {
+ if (filter instanceof InputFilter.LengthFilter) {
+ maxLength = ((InputFilter.LengthFilter) filter).getMax();
+ break;
+ }
+ }
+ structure.setMaxTextLength(maxLength);
}
}
structure.setHint(getHint());
diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java
index c5c38f53..60ec8a96 100644
--- a/com/android/commands/pm/Pm.java
+++ b/com/android/commands/pm/Pm.java
@@ -1570,11 +1570,19 @@ public final class Pm {
private static int showUsage() {
System.err.println("usage: pm path [--user USER_ID] PACKAGE");
System.err.println(" pm dump PACKAGE");
- System.err.println(" pm install [-lrtsfd] [-i PACKAGE] [--user USER_ID] [PATH]");
- System.err.println(" pm install-create [-lrtsfdp] [-i PACKAGE] [-S BYTES]");
- System.err.println(" [--install-location 0/1/2]");
- System.err.println(" [--force-uuid internal|UUID]");
- System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH]");
+ System.err.println(" pm install [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
+ System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+ System.err.println(" [--originating-uri URI] [---referrer URI]");
+ System.err.println(" [--abi ABI_NAME] [--force-sdk]");
+ System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]");
+ System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]");
+ System.err.println(" pm install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
+ System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+ System.err.println(" [--originating-uri URI] [---referrer URI]");
+ System.err.println(" [--abi ABI_NAME] [--force-sdk]");
+ System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]");
+ System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
+ System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]");
System.err.println(" pm install-commit SESSION_ID");
System.err.println(" pm install-abandon SESSION_ID");
System.err.println(" pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE");
@@ -1613,15 +1621,27 @@ public final class Pm {
System.err.println("pm install: install a single legacy package");
System.err.println("pm install-create: create an install session");
System.err.println(" -l: forward lock application");
- System.err.println(" -r: replace existing application");
+ System.err.println(" -r: allow replacement of existing application");
System.err.println(" -t: allow test packages");
- System.err.println(" -i: specify the installer package name");
+ System.err.println(" -i: specify package name of installer owning the app");
System.err.println(" -s: install application on sdcard");
System.err.println(" -f: install application on internal flash");
System.err.println(" -d: allow version code downgrade (debuggable packages only)");
- System.err.println(" -p: partial application install");
+ System.err.println(" -p: partial application install (new split on top of existing pkg)");
System.err.println(" -g: grant all runtime permissions");
System.err.println(" -S: size in bytes of entire session");
+ System.err.println(" --dont-kill: installing a new feature split, don't kill running app");
+ System.err.println(" --originating-uri: set URI where app was downloaded from");
+ System.err.println(" --referrer: set URI that instigated the install of the app");
+ System.err.println(" --pkg: specify expected package name of app being installed");
+ System.err.println(" --abi: override the default ABI of the platform");
+ System.err.println(" --instantapp: cause the app to be installed as an ephemeral install app");
+ System.err.println(" --full: cause the app to be installed as a non-ephemeral full app");
+ System.err.println(" --install-location: force the install location:");
+ System.err.println(" 0=auto, 1=internal only, 2=prefer external");
+ System.err.println(" --force-uuid: force install on to disk volume with given UUID");
+ System.err.println(" --force-sdk: allow install even when existing app targets platform");
+ System.err.println(" codename but new one targets a final API level");
System.err.println("");
System.err.println("pm install-write: write a package into existing session; path may");
System.err.println(" be '-' to read from stdin");
diff --git a/com/android/ex/photo/ActionBarWrapper.java b/com/android/ex/photo/ActionBarWrapper.java
index ae621979..6d4d4d20 100644
--- a/com/android/ex/photo/ActionBarWrapper.java
+++ b/com/android/ex/photo/ActionBarWrapper.java
@@ -1,8 +1,7 @@
package com.android.ex.photo;
-
+import android.app.ActionBar;
import android.graphics.drawable.Drawable;
-import android.support.v7.app.ActionBar;
/**
* Wrapper around {@link ActionBar}.
diff --git a/com/android/ex/photo/PhotoViewActivity.java b/com/android/ex/photo/PhotoViewActivity.java
index a5c4a438..7b53918f 100644
--- a/com/android/ex/photo/PhotoViewActivity.java
+++ b/com/android/ex/photo/PhotoViewActivity.java
@@ -21,14 +21,14 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
+import android.support.v4.app.FragmentActivity;
import android.view.Menu;
import android.view.MenuItem;
/**
* Activity to view the contents of an album.
*/
-public class PhotoViewActivity extends AppCompatActivity
+public class PhotoViewActivity extends FragmentActivity
implements PhotoViewController.ActivityInterface {
private PhotoViewController mController;
@@ -41,7 +41,7 @@ public class PhotoViewActivity extends AppCompatActivity
mController.onCreate(savedInstanceState);
}
- protected PhotoViewController createController() {
+ public PhotoViewController createController() {
return new PhotoViewController(this);
}
@@ -122,7 +122,7 @@ public class PhotoViewActivity extends AppCompatActivity
@Override
public ActionBarInterface getActionBarInterface() {
if (mActionBar == null) {
- mActionBar = new ActionBarWrapper(getSupportActionBar());
+ mActionBar = new ActionBarWrapper(getActionBar());
}
return mActionBar;
}
diff --git a/com/android/internal/alsa/AlsaCardsParser.java b/com/android/internal/alsa/AlsaCardsParser.java
index 5b92a173..bb75bf6e 100644
--- a/com/android/internal/alsa/AlsaCardsParser.java
+++ b/com/android/internal/alsa/AlsaCardsParser.java
@@ -37,6 +37,12 @@ public class AlsaCardsParser {
private ArrayList<AlsaCardRecord> mCardRecords = new ArrayList<AlsaCardRecord>();
+ public static final int SCANSTATUS_NOTSCANNED = -1;
+ public static final int SCANSTATUS_SUCCESS = 0;
+ public static final int SCANSTATUS_FAIL = 1;
+ public static final int SCANSTATUS_EMPTY = 2;
+ private int mScanStatus = SCANSTATUS_NOTSCANNED;
+
public class AlsaCardRecord {
private static final String TAG = "AlsaCardRecord";
private static final String kUsbCardKeyStr = "at usb-";
@@ -104,10 +110,11 @@ public class AlsaCardsParser {
public AlsaCardsParser() {}
- public void scan() {
+ public int scan() {
if (DEBUG) {
- Slog.i(TAG, "AlsaCardsParser.scan()");
+ Slog.i(TAG, "AlsaCardsParser.scan()....");
}
+
mCardRecords = new ArrayList<AlsaCardRecord>();
File cardsFile = new File(kCardsFilePath);
@@ -134,11 +141,26 @@ public class AlsaCardsParser {
mCardRecords.add(cardRecord);
}
reader.close();
+ if (mCardRecords.size() > 0) {
+ mScanStatus = SCANSTATUS_SUCCESS;
+ } else {
+ mScanStatus = SCANSTATUS_EMPTY;
+ }
} catch (FileNotFoundException e) {
e.printStackTrace();
+ mScanStatus = SCANSTATUS_FAIL;
} catch (IOException e) {
e.printStackTrace();
+ mScanStatus = SCANSTATUS_FAIL;
+ }
+ if (DEBUG) {
+ Slog.i(TAG, " status:" + mScanStatus);
}
+ return mScanStatus;
+ }
+
+ public int getScanStatus() {
+ return mScanStatus;
}
public ArrayList<AlsaCardRecord> getScanRecords() {
@@ -182,7 +204,11 @@ public class AlsaCardsParser {
}
// get the new list of devices
- scan();
+ if (scan() != SCANSTATUS_SUCCESS) {
+ Slog.e(TAG, "Error scanning Alsa cards file.");
+ return -1;
+ }
+
if (DEBUG) {
LogDevices("Current Devices:", mCardRecords);
}
diff --git a/com/android/internal/alsa/AlsaDevicesParser.java b/com/android/internal/alsa/AlsaDevicesParser.java
index 6e3d5966..15261baf 100644
--- a/com/android/internal/alsa/AlsaDevicesParser.java
+++ b/com/android/internal/alsa/AlsaDevicesParser.java
@@ -46,6 +46,12 @@ public class AlsaDevicesParser {
private boolean mHasPlaybackDevices = false;
private boolean mHasMIDIDevices = false;
+ public static final int SCANSTATUS_NOTSCANNED = -1;
+ public static final int SCANSTATUS_SUCCESS = 0;
+ public static final int SCANSTATUS_FAIL = 1;
+ public static final int SCANSTATUS_EMPTY = 2;
+ private int mScanStatus = SCANSTATUS_NOTSCANNED;
+
public class AlsaDeviceRecord {
public static final int kDeviceType_Unknown = -1;
public static final int kDeviceType_Audio = 0;
@@ -258,7 +264,11 @@ public class AlsaDevicesParser {
return line.charAt(kIndex_CardDeviceField) == '[';
}
- public boolean scan() {
+ public int scan() {
+ if (DEBUG) {
+ Slog.i(TAG, "AlsaDevicesParser.scan()....");
+ }
+
mDeviceRecords.clear();
File devicesFile = new File(kDevicesFilePath);
@@ -274,13 +284,27 @@ public class AlsaDevicesParser {
}
}
reader.close();
- return true;
+ // success if we add at least 1 record
+ if (mDeviceRecords.size() > 0) {
+ mScanStatus = SCANSTATUS_SUCCESS;
+ } else {
+ mScanStatus = SCANSTATUS_EMPTY;
+ }
} catch (FileNotFoundException e) {
e.printStackTrace();
+ mScanStatus = SCANSTATUS_FAIL;
} catch (IOException e) {
e.printStackTrace();
+ mScanStatus = SCANSTATUS_FAIL;
}
- return false;
+ if (DEBUG) {
+ Slog.i(TAG, " status:" + mScanStatus);
+ }
+ return mScanStatus;
+ }
+
+ public int getScanStatus() {
+ return mScanStatus;
}
//
diff --git a/com/android/internal/notification/SystemNotificationChannels.java b/com/android/internal/notification/SystemNotificationChannels.java
index d64c9a1d..4a181b27 100644
--- a/com/android/internal/notification/SystemNotificationChannels.java
+++ b/com/android/internal/notification/SystemNotificationChannels.java
@@ -20,6 +20,7 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ParceledListSlice;
+import android.media.AudioAttributes;
import android.os.RemoteException;
import android.provider.Settings;
@@ -47,6 +48,7 @@ public class SystemNotificationChannels {
public static String RETAIL_MODE = "RETAIL_MODE";
public static String USB = "USB";
public static String FOREGROUND_SERVICE = "FOREGROUND_SERVICE";
+ public static String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
public static void createAll(Context context) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -139,6 +141,17 @@ public class SystemNotificationChannels {
foregroundChannel.setBlockableSystem(true);
channelsList.add(foregroundChannel);
+ NotificationChannel heavyWeightChannel = new NotificationChannel(
+ HEAVY_WEIGHT_APP,
+ context.getString(R.string.notification_channel_heavy_weight_app),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ heavyWeightChannel.setShowBadge(false);
+ heavyWeightChannel.setSound(null, new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT)
+ .build());
+ channelsList.add(heavyWeightChannel);
+
nm.createNotificationChannels(channelsList);
}
diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java
index 36fd991c..5c310b15 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 = 167 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 168 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -681,17 +681,17 @@ public class BatteryStatsImpl extends BatteryStats {
}
@Override
- public long getMahDischarge(int which) {
+ public long getUahDischarge(int which) {
return mDischargeCounter.getCountLocked(which);
}
@Override
- public long getMahDischargeScreenOff(int which) {
+ public long getUahDischargeScreenOff(int which) {
return mDischargeScreenOffCounter.getCountLocked(which);
}
@Override
- public long getMahDischargeScreenDoze(int which) {
+ public long getUahDischargeScreenDoze(int which) {
return mDischargeScreenDozeCounter.getCountLocked(which);
}
@@ -3588,7 +3588,7 @@ public class BatteryStatsImpl extends BatteryStats {
public void updateTimeBasesLocked(boolean unplugged, int screenState, long uptime,
long realtime) {
- final boolean screenOff = isScreenOff(screenState) || isScreenDoze(screenState);
+ final boolean screenOff = !isScreenOn(screenState);
final boolean updateOnBatteryTimeBase = unplugged != mOnBatteryTimeBase.isRunning();
final boolean updateOnBatteryScreenOffTimeBase =
(unplugged && screenOff) != mOnBatteryScreenOffTimeBase.isRunning();
@@ -5427,6 +5427,10 @@ public class BatteryStatsImpl extends BatteryStats {
elapsedRealtimeUs, which);
}
+ @Override public Timer getScreenBrightnessTimer(int brightnessBin) {
+ return mScreenBrightnessTimer[brightnessBin];
+ }
+
@Override public long getInteractiveTime(long elapsedRealtimeUs, int which) {
return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@@ -5520,10 +5524,18 @@ public class BatteryStatsImpl extends BatteryStats {
elapsedRealtimeUs, which);
}
+ @Override public Timer getPhoneSignalScanningTimer() {
+ return mPhoneSignalScanningTimer;
+ }
+
@Override public int getPhoneSignalStrengthCount(int strengthBin, int which) {
return mPhoneSignalStrengthsTimer[strengthBin].getCountLocked(which);
}
+ @Override public Timer getPhoneSignalStrengthTimer(int strengthBin) {
+ return mPhoneSignalStrengthsTimer[strengthBin];
+ }
+
@Override public long getPhoneDataConnectionTime(int dataType,
long elapsedRealtimeUs, int which) {
return mPhoneDataConnectionsTimer[dataType].getTotalTimeLocked(
@@ -5534,6 +5546,10 @@ public class BatteryStatsImpl extends BatteryStats {
return mPhoneDataConnectionsTimer[dataType].getCountLocked(which);
}
+ @Override public Timer getPhoneDataConnectionTimer(int dataType) {
+ return mPhoneDataConnectionsTimer[dataType];
+ }
+
@Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) {
return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@@ -5572,6 +5588,10 @@ public class BatteryStatsImpl extends BatteryStats {
return mWifiStateTimer[wifiState].getCountLocked(which);
}
+ @Override public Timer getWifiStateTimer(int wifiState) {
+ return mWifiStateTimer[wifiState];
+ }
+
@Override public long getWifiSupplStateTime(int state,
long elapsedRealtimeUs, int which) {
return mWifiSupplStateTimer[state].getTotalTimeLocked(
@@ -5582,6 +5602,10 @@ public class BatteryStatsImpl extends BatteryStats {
return mWifiSupplStateTimer[state].getCountLocked(which);
}
+ @Override public Timer getWifiSupplStateTimer(int state) {
+ return mWifiSupplStateTimer[state];
+ }
+
@Override public long getWifiSignalStrengthTime(int strengthBin,
long elapsedRealtimeUs, int which) {
return mWifiSignalStrengthsTimer[strengthBin].getTotalTimeLocked(
@@ -5592,6 +5616,10 @@ public class BatteryStatsImpl extends BatteryStats {
return mWifiSignalStrengthsTimer[strengthBin].getCountLocked(which);
}
+ @Override public Timer getWifiSignalStrengthTimer(int strengthBin) {
+ return mWifiSignalStrengthsTimer[strengthBin];
+ }
+
@Override
public ControllerActivityCounter getBluetoothControllerActivity() {
return mBluetoothActivity;
@@ -9463,7 +9491,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
public boolean isScreenOn(int state) {
- return state == Display.STATE_ON;
+ return state == Display.STATE_ON || state == Display.STATE_VR;
}
public boolean isScreenOff(int state) {
@@ -12791,7 +12819,7 @@ public class BatteryStatsImpl extends BatteryStats {
mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
mMobileRadioActiveTimer = new StopwatchTimer(mClocks, null, -400, null,
mOnBatteryTimeBase, in);
- mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null,
+ mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null,
mOnBatteryTimeBase, in);
mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
@@ -13090,7 +13118,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
} else {
- // TODO: There should be two 0's printed here, not just one.
+ out.writeInt(0);
out.writeInt(0);
}
diff --git a/com/android/internal/os/LoggingPrintStream.java b/com/android/internal/os/LoggingPrintStream.java
index f14394ad..d27874cd 100644
--- a/com/android/internal/os/LoggingPrintStream.java
+++ b/com/android/internal/os/LoggingPrintStream.java
@@ -28,12 +28,15 @@ import java.nio.charset.CodingErrorAction;
import java.util.Formatter;
import java.util.Locale;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* A print stream which logs output line by line.
*
* {@hide}
*/
-abstract class LoggingPrintStream extends PrintStream {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public abstract class LoggingPrintStream extends PrintStream {
private final StringBuilder builder = new StringBuilder();
diff --git a/com/android/internal/os/ZygoteInit.java b/com/android/internal/os/ZygoteInit.java
index 4abab283..2be6212b 100644
--- a/com/android/internal/os/ZygoteInit.java
+++ b/com/android/internal/os/ZygoteInit.java
@@ -549,7 +549,7 @@ public class ZygoteInit {
try {
dexoptNeeded = DexFile.getDexOptNeeded(
classPathElement, instructionSet, systemServerFilter,
- false /* newProfile */, false /* downgrade */);
+ null /* classLoaderContext */, false /* newProfile */, false /* downgrade */);
} catch (FileNotFoundException ignored) {
// Do not add to the classpath.
Log.w(TAG, "Missing classpath element for system server: " + classPathElement);
diff --git a/com/android/internal/telephony/CarrierKeyDownloadManager.java b/com/android/internal/telephony/CarrierKeyDownloadManager.java
index 606f7ffd..66bc5291 100644
--- a/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -16,6 +16,10 @@
package com.android.internal.telephony;
+import static android.preference.PreferenceManager.getDefaultSharedPreferences;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.app.AlarmManager;
import android.app.DownloadManager;
import android.app.PendingIntent;
@@ -53,8 +57,7 @@ import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
-
-import static android.preference.PreferenceManager.getDefaultSharedPreferences;
+import java.util.zip.GZIPInputStream;
/**
* This class contains logic to get Certificates and keep them current.
@@ -82,7 +85,7 @@ public class CarrierKeyDownloadManager {
private static final String SEPARATOR = ":";
private static final String JSON_CERTIFICATE = "certificate";
- // This is a hack to accomodate Verizon. Verizon insists on using the public-key
+ // This is a hack to accommodate certain Carriers who 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";
@@ -296,6 +299,7 @@ public class CarrierKeyDownloadManager {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(carrierKeyDownloadIdentifier);
Cursor cursor = mDownloadManager.query(query);
+ InputStream source = null;
if (cursor == null) {
return;
@@ -304,7 +308,7 @@ public class CarrierKeyDownloadManager {
int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == cursor.getInt(columnIndex)) {
try {
- final InputStream source = new FileInputStream(
+ source = new FileInputStream(
mDownloadManager.openDownloadedFile(carrierKeyDownloadIdentifier)
.getFileDescriptor());
jsonStr = convertToString(source);
@@ -314,6 +318,11 @@ public class CarrierKeyDownloadManager {
+ ". " + e);
} finally {
mDownloadManager.remove(carrierKeyDownloadIdentifier);
+ try {
+ source.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
}
}
Log.d(LOG_TAG, "Completed downloading keys");
@@ -353,24 +362,23 @@ public class CarrierKeyDownloadManager {
}
private static String convertToString(InputStream is) {
- BufferedReader reader = new BufferedReader(new InputStreamReader(is));
- StringBuilder sb = new StringBuilder();
-
- String line;
try {
+ // The current implementation at certain Carriers has the data gzipped, which requires
+ // us to unzip the contents. Longer term, we want to add a flag in carrier config which
+ // determines if the data needs to be zipped or not.
+ GZIPInputStream gunzip = new GZIPInputStream(is);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(gunzip, UTF_8));
+ StringBuilder sb = new StringBuilder();
+
+ String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append('\n');
}
+ return sb.toString();
} catch (IOException e) {
e.printStackTrace();
- } finally {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
}
- return sb.toString();
+ return null;
}
/**
@@ -401,7 +409,7 @@ public class CarrierKeyDownloadManager {
JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS);
for (int i = 0; i < keys.length(); i++) {
JSONObject key = keys.getJSONObject(i);
- // This is a hack to accomodate Verizon. Verizon insists on using the public-key
+ // This is a hack to accommodate certain carriers who insist 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)) {
diff --git a/com/android/internal/telephony/NetworkScanRequestTracker.java b/com/android/internal/telephony/NetworkScanRequestTracker.java
index 14c6810c..46b1eef9 100644
--- a/com/android/internal/telephony/NetworkScanRequestTracker.java
+++ b/com/android/internal/telephony/NetworkScanRequestTracker.java
@@ -125,6 +125,34 @@ public final class NetworkScanRequestTracker {
return false;
}
}
+
+ if ((nsri.mRequest.searchPeriodicity < NetworkScanRequest.MIN_SEARCH_PERIODICITY_SEC)
+ || (nsri.mRequest.searchPeriodicity
+ > NetworkScanRequest.MAX_SEARCH_PERIODICITY_SEC)) {
+ return false;
+ }
+
+ if ((nsri.mRequest.maxSearchTime < NetworkScanRequest.MIN_SEARCH_MAX_SEC)
+ || (nsri.mRequest.maxSearchTime > NetworkScanRequest.MAX_SEARCH_MAX_SEC)) {
+ return false;
+ }
+
+ if ((nsri.mRequest.incrementalResultsPeriodicity
+ < NetworkScanRequest.MIN_INCREMENTAL_PERIODICITY_SEC)
+ || (nsri.mRequest.incrementalResultsPeriodicity
+ > NetworkScanRequest.MAX_INCREMENTAL_PERIODICITY_SEC)) {
+ return false;
+ }
+
+ if ((nsri.mRequest.searchPeriodicity > nsri.mRequest.maxSearchTime)
+ || (nsri.mRequest.incrementalResultsPeriodicity > nsri.mRequest.maxSearchTime)) {
+ return false;
+ }
+
+ if ((nsri.mRequest.mccMncs != null)
+ && (nsri.mRequest.mccMncs.size() > NetworkScanRequest.MAX_MCC_MNC_LIST_SIZE)) {
+ return false;
+ }
return true;
}
diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java
index 6acc8743..16f816f4 100644
--- a/com/android/internal/telephony/Phone.java
+++ b/com/android/internal/telephony/Phone.java
@@ -3319,7 +3319,9 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
mRadioCapability.set(rc);
if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
- sendSubscriptionSettings(true);
+ boolean restoreSelection = !mContext.getResources().getBoolean(
+ com.android.internal.R.bool.skip_restoring_network_selection);
+ sendSubscriptionSettings(restoreSelection);
}
}
diff --git a/com/android/internal/telephony/RIL.java b/com/android/internal/telephony/RIL.java
index 84c2b659..9007f14d 100644
--- a/com/android/internal/telephony/RIL.java
+++ b/com/android/internal/telephony/RIL.java
@@ -1655,56 +1655,69 @@ public class RIL extends BaseCommands implements CommandsInterface {
}
}
+ private android.hardware.radio.V1_1.RadioAccessSpecifier convertRadioAccessSpecifierToRadioHAL(
+ RadioAccessSpecifier ras) {
+ android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
+ new android.hardware.radio.V1_1.RadioAccessSpecifier();
+ rasInHalFormat.radioAccessNetwork = ras.radioAccessNetwork;
+ List<Integer> bands = null;
+ switch (ras.radioAccessNetwork) {
+ case RadioAccessNetworks.GERAN:
+ bands = rasInHalFormat.geranBands;
+ break;
+ case RadioAccessNetworks.UTRAN:
+ bands = rasInHalFormat.utranBands;
+ break;
+ case RadioAccessNetworks.EUTRAN:
+ bands = rasInHalFormat.eutranBands;
+ break;
+ default:
+ Log.wtf(RILJ_LOG_TAG, "radioAccessNetwork " + ras.radioAccessNetwork
+ + " not supported!");
+ return null;
+ }
+
+ if (ras.bands != null) {
+ for (int band : ras.bands) {
+ bands.add(band);
+ }
+ }
+ if (ras.channels != null) {
+ for (int channel : ras.channels) {
+ rasInHalFormat.channels.add(channel);
+ }
+ }
+
+ return rasInHalFormat;
+ }
+
@Override
public void startNetworkScan(NetworkScanRequest nsr, Message result) {
IRadio radioProxy = getRadioProxy(result);
if (radioProxy != null) {
- android.hardware.radio.V1_1.IRadio radioProxy11 =
- android.hardware.radio.V1_1.IRadio.castFrom(radioProxy);
- if (radioProxy11 == null) {
- if (result != null) {
- AsyncResult.forMessage(result, null,
- CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
- result.sendToTarget();
- }
- } else {
- android.hardware.radio.V1_1.NetworkScanRequest request =
- new android.hardware.radio.V1_1.NetworkScanRequest();
+ android.hardware.radio.V1_2.IRadio radioProxy12 =
+ android.hardware.radio.V1_2.IRadio.castFrom(radioProxy);
+ if (radioProxy12 != null) {
+ android.hardware.radio.V1_2.NetworkScanRequest request =
+ new android.hardware.radio.V1_2.NetworkScanRequest();
request.type = nsr.scanType;
- request.interval = 60;
+ request.interval = nsr.searchPeriodicity;
+ request.maxSearchTime = nsr.maxSearchTime;
+ request.incrementalResultsPeriodicity = nsr.incrementalResultsPeriodicity;
+ request.incrementalResults = nsr.incrementalResults;
+
for (RadioAccessSpecifier ras : nsr.specifiers) {
- android.hardware.radio.V1_1.RadioAccessSpecifier s =
- new android.hardware.radio.V1_1.RadioAccessSpecifier();
- s.radioAccessNetwork = ras.radioAccessNetwork;
- List<Integer> bands = null;
- switch (ras.radioAccessNetwork) {
- case RadioAccessNetworks.GERAN:
- bands = s.geranBands;
- break;
- case RadioAccessNetworks.UTRAN:
- bands = s.utranBands;
- break;
- case RadioAccessNetworks.EUTRAN:
- bands = s.eutranBands;
- break;
- default:
- Log.wtf(RILJ_LOG_TAG, "radioAccessNetwork " + ras.radioAccessNetwork
- + " not supported!");
- return;
- }
- if (ras.bands != null) {
- for (int band : ras.bands) {
- bands.add(band);
- }
- }
- if (ras.channels != null) {
- for (int channel : ras.channels) {
- s.channels.add(channel);
- }
+
+ android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
+ convertRadioAccessSpecifierToRadioHAL(ras);
+ if (rasInHalFormat == null) {
+ return;
}
- request.specifiers.add(s);
+
+ request.specifiers.add(rasInHalFormat);
}
+ request.mccMncs.addAll(nsr.mccMncs);
RILRequest rr = obtainRequest(RIL_REQUEST_START_NETWORK_SCAN, result,
mRILDefaultWorkSource);
@@ -1713,10 +1726,47 @@ public class RIL extends BaseCommands implements CommandsInterface {
}
try {
- radioProxy11.startNetworkScan(rr.mSerial, request);
+ radioProxy12.startNetworkScan_1_2(rr.mSerial, request);
} catch (RemoteException | RuntimeException e) {
handleRadioProxyExceptionForRR(rr, "startNetworkScan", e);
}
+ } else {
+ android.hardware.radio.V1_1.IRadio radioProxy11 =
+ android.hardware.radio.V1_1.IRadio.castFrom(radioProxy);
+ if (radioProxy11 == null) {
+ if (result != null) {
+ AsyncResult.forMessage(result, null,
+ CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+ result.sendToTarget();
+ }
+ } else {
+ android.hardware.radio.V1_1.NetworkScanRequest request =
+ new android.hardware.radio.V1_1.NetworkScanRequest();
+ request.type = nsr.scanType;
+ request.interval = nsr.searchPeriodicity;
+ for (RadioAccessSpecifier ras : nsr.specifiers) {
+ android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
+ convertRadioAccessSpecifierToRadioHAL(ras);
+ if (rasInHalFormat == null) {
+ return;
+ }
+
+ request.specifiers.add(rasInHalFormat);
+ }
+
+ RILRequest rr = obtainRequest(RIL_REQUEST_START_NETWORK_SCAN, result,
+ mRILDefaultWorkSource);
+
+ if (RILJ_LOGD) {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+ }
+
+ try {
+ radioProxy11.startNetworkScan(rr.mSerial, request);
+ } catch (RemoteException | RuntimeException e) {
+ handleRadioProxyExceptionForRR(rr, "startNetworkScan", e);
+ }
+ }
}
}
}
diff --git a/com/android/internal/telephony/cat/CatService.java b/com/android/internal/telephony/cat/CatService.java
index cd7a7561..802944d8 100644
--- a/com/android/internal/telephony/cat/CatService.java
+++ b/com/android/internal/telephony/cat/CatService.java
@@ -1071,6 +1071,13 @@ public class CatService extends Handler implements AppInterface {
}
break;
case NO_RESPONSE_FROM_USER:
+ // No need to send terminal response for SET UP CALL on user timeout,
+ // instead use dedicated API
+ if (type == CommandType.SET_UP_CALL) {
+ mCmdIf.handleCallSetupRequestFromSim(false, null);
+ mCurrntCmd = null;
+ return;
+ }
case UICC_SESSION_TERM_BY_USER:
resp = null;
break;
diff --git a/com/android/internal/telephony/uicc/UiccCardApplication.java b/com/android/internal/telephony/uicc/UiccCardApplication.java
index e2904dfd..fa6bc3a6 100644
--- a/com/android/internal/telephony/uicc/UiccCardApplication.java
+++ b/com/android/internal/telephony/uicc/UiccCardApplication.java
@@ -382,11 +382,8 @@ public class UiccCardApplication {
case EVENT_CHANGE_PIN2_DONE:
// a PIN/PUK/PIN2/PUK2 complete
// request has completed. ar.userObj is the response Message
- int attemptsRemaining = -1;
ar = (AsyncResult)msg.obj;
- if ((ar.exception != null) && (ar.result != null)) {
- attemptsRemaining = parsePinPukErrorResult(ar);
- }
+ int attemptsRemaining = parsePinPukErrorResult(ar);
Message response = (Message)ar.userObj;
AsyncResult.forMessage(response).exception = ar.exception;
response.arg1 = attemptsRemaining;
diff --git a/com/android/internal/util/MemInfoReader.java b/com/android/internal/util/MemInfoReader.java
index b71fa067..8d716667 100644
--- a/com/android/internal/util/MemInfoReader.java
+++ b/com/android/internal/util/MemInfoReader.java
@@ -82,7 +82,7 @@ public final class MemInfoReader {
* that are mapped in to processes.
*/
public long getCachedSizeKb() {
- return mInfos[Debug.MEMINFO_BUFFERS]
+ return mInfos[Debug.MEMINFO_BUFFERS] + mInfos[Debug.MEMINFO_SLAB_RECLAIMABLE]
+ mInfos[Debug.MEMINFO_CACHED] - mInfos[Debug.MEMINFO_MAPPED];
}
@@ -90,7 +90,7 @@ public final class MemInfoReader {
* Amount of RAM that is in use by the kernel for actual allocations.
*/
public long getKernelUsedSizeKb() {
- return mInfos[Debug.MEMINFO_SHMEM] + mInfos[Debug.MEMINFO_SLAB]
+ return mInfos[Debug.MEMINFO_SHMEM] + mInfos[Debug.MEMINFO_SLAB_UNRECLAIMABLE]
+ mInfos[Debug.MEMINFO_VM_ALLOC_USED] + mInfos[Debug.MEMINFO_PAGE_TABLES]
+ mInfos[Debug.MEMINFO_KERNEL_STACK];
}
diff --git a/com/android/internal/view/menu/ListMenuItemView.java b/com/android/internal/view/menu/ListMenuItemView.java
index f76c7247..8f80bfe3 100644
--- a/com/android/internal/view/menu/ListMenuItemView.java
+++ b/com/android/internal/view/menu/ListMenuItemView.java
@@ -319,13 +319,15 @@ public class ListMenuItemView extends LinearLayout
public void setGroupDividerEnabled(boolean groupDividerEnabled) {
// If mHasListDivider is true, disabling the groupDivider.
// Otherwise, checking enbling it according to groupDividerEnabled flag.
- mGroupDivider.setVisibility(!mHasListDivider
- && groupDividerEnabled ? View.VISIBLE : View.GONE);
+ if (mGroupDivider != null) {
+ mGroupDivider.setVisibility(!mHasListDivider
+ && groupDividerEnabled ? View.VISIBLE : View.GONE);
+ }
}
@Override
public void adjustListItemSelectionBounds(Rect rect) {
- if (mGroupDivider.getVisibility() == View.VISIBLE) {
+ if (mGroupDivider != null && mGroupDivider.getVisibility() == View.VISIBLE) {
// groupDivider is a part of MenuItemListView.
// If ListMenuItem with divider enabled is hovered/clicked, divider also gets selected.
// Clipping the selector bounds from the top divider portion when divider is enabled,
diff --git a/com/android/internal/widget/Magnifier.java b/com/android/internal/widget/Magnifier.java
index 86e7b38a..9bc0778d 100644
--- a/com/android/internal/widget/Magnifier.java
+++ b/com/android/internal/widget/Magnifier.java
@@ -22,7 +22,9 @@ import android.annotation.UiThread;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Handler;
import android.util.Log;
import android.view.Gravity;
@@ -41,6 +43,8 @@ import com.android.internal.util.Preconditions;
*/
public final class Magnifier {
private static final String LOG_TAG = "magnifier";
+ // Use this to specify that a previous configuration value does not exist.
+ private static final int INEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
// The view for which this magnifier is attached.
private final View mView;
// The window containing the magnifier.
@@ -59,6 +63,15 @@ public final class Magnifier {
// the copy is finished.
private final Handler mPixelCopyHandler = Handler.getMain();
+ private RectF mTmpRectF;
+
+ // Variables holding previous states, used for detecting redundant calls and invalidation.
+ private Point mPrevStartCoordsOnScreen = new Point(
+ INEXISTENT_PREVIOUS_CONFIG_VALUE, INEXISTENT_PREVIOUS_CONFIG_VALUE);
+ private PointF mPrevCenterCoordsOnScreen = new PointF(
+ INEXISTENT_PREVIOUS_CONFIG_VALUE, INEXISTENT_PREVIOUS_CONFIG_VALUE);
+ private float mPrevScale = INEXISTENT_PREVIOUS_CONFIG_VALUE;
+
/**
* Initializes a magnifier.
*
@@ -88,16 +101,45 @@ public final class Magnifier {
/**
* 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
+ * @param centerXOnScreen horizontal coordinate of the center point of the magnifier source. The
+ * lower end is clamped to 0
+ * @param centerYOnScreen vertical coordinate of the center point of the magnifier source. The
+ * lower end is clamped to 0
+ * @param scale the scale at which the magnifier zooms on the source content. The
+ * lower end is clamped to 1 and the higher end to 4
*/
public void show(@FloatRange(from=0) float centerXOnScreen,
@FloatRange(from=0) float centerYOnScreen,
- @FloatRange(from=1, to=10) float scale) {
- maybeResizeBitmap(scale);
+ @FloatRange(from=1, to=4) float scale) {
+ if (scale > 4) {
+ scale = 4;
+ }
+
+ if (scale < 1) {
+ scale = 1;
+ }
+
+ if (centerXOnScreen < 0) {
+ centerXOnScreen = 0;
+ }
+
+ if (centerYOnScreen < 0) {
+ centerYOnScreen = 0;
+ }
+
+ showInternal(centerXOnScreen, centerYOnScreen, scale, false);
+ }
+
+ private void showInternal(@FloatRange(from=0) float centerXOnScreen,
+ @FloatRange(from=0) float centerYOnScreen,
+ @FloatRange(from=1, to=4) float scale,
+ boolean forceShow) {
+ if (mPrevScale != scale) {
+ resizeBitmap(scale);
+ mPrevScale = scale;
+ }
configureCoordinates(centerXOnScreen, centerYOnScreen);
- performPixelCopy();
+ maybePerformPixelCopy(scale, forceShow);
if (mWindow.isShowing()) {
mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
@@ -106,6 +148,9 @@ public final class Magnifier {
mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY,
mWindowCoords.x, mWindowCoords.y);
}
+
+ mPrevCenterCoordsOnScreen.x = centerXOnScreen;
+ mPrevCenterCoordsOnScreen.y = centerYOnScreen;
}
/**
@@ -113,6 +158,38 @@ public final class Magnifier {
*/
public void dismiss() {
mWindow.dismiss();
+
+ mPrevStartCoordsOnScreen.x = INEXISTENT_PREVIOUS_CONFIG_VALUE;
+ mPrevStartCoordsOnScreen.y = INEXISTENT_PREVIOUS_CONFIG_VALUE;
+ mPrevCenterCoordsOnScreen.x = INEXISTENT_PREVIOUS_CONFIG_VALUE;
+ mPrevCenterCoordsOnScreen.y = INEXISTENT_PREVIOUS_CONFIG_VALUE;
+ mPrevScale = INEXISTENT_PREVIOUS_CONFIG_VALUE;
+ }
+
+ /**
+ * Forces the magnifier to update content by taking and showing a new snapshot using the
+ * previous coordinates. It does this only if the magnifier is showing and the dirty rectangle
+ * intersects the rectangle which holds the content to be magnified.
+ *
+ * @param dirtyRectOnScreen the rectangle representing the screen bounds of the dirty region
+ */
+ public void invalidate(RectF dirtyRectOnScreen) {
+ if (mWindow.isShowing() && mPrevCenterCoordsOnScreen.x != INEXISTENT_PREVIOUS_CONFIG_VALUE
+ && mPrevCenterCoordsOnScreen.y != INEXISTENT_PREVIOUS_CONFIG_VALUE
+ && mPrevScale != INEXISTENT_PREVIOUS_CONFIG_VALUE) {
+ // Update the current showing RectF.
+ mTmpRectF = new RectF(mPrevStartCoordsOnScreen.x,
+ mPrevStartCoordsOnScreen.y,
+ mPrevStartCoordsOnScreen.x + mBitmap.getWidth(),
+ mPrevStartCoordsOnScreen.y + mBitmap.getHeight());
+
+ // Update only if we are currently showing content that has been declared as invalid.
+ if (RectF.intersects(dirtyRectOnScreen, mTmpRectF)) {
+ // Update the contents shown in the magnifier.
+ showInternal(mPrevCenterCoordsOnScreen.x, mPrevCenterCoordsOnScreen.y, mPrevScale,
+ true /* forceShow */);
+ }
+ }
}
/**
@@ -129,13 +206,11 @@ public final class Magnifier {
return mWindowWidth;
}
- private void maybeResizeBitmap(float scale) {
+ private void resizeBitmap(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);
- }
+ mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+ getImageView().setImageBitmap(mBitmap);
}
private void configureCoordinates(float posXOnScreen, float posYOnScreen) {
@@ -144,24 +219,29 @@ public final class Magnifier {
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);
+ mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset;
}
- private void performPixelCopy() {
- int startX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
+ private void maybePerformPixelCopy(final float scale, final boolean forceShow) {
+ final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
+ int rawStartX = 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();
+ if (rawStartX < 0) {
+ rawStartX = 0;
+ } else if (rawStartX + mBitmap.getWidth() > mView.getWidth()) {
+ rawStartX = mView.getWidth() - mBitmap.getWidth();
}
- final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
+ if (!forceShow && rawStartX == mPrevStartCoordsOnScreen.x
+ && startY == mPrevStartCoordsOnScreen.y
+ && scale == mPrevScale) {
+ // Skip, we are already showing the desired content.
+ return;
+ }
+
+ final int startX = rawStartX;
final ViewRootImpl viewRootImpl = mView.getViewRootImpl();
if (viewRootImpl != null && viewRootImpl.mSurface != null
@@ -171,7 +251,11 @@ public final class Magnifier {
new Rect(startX, startY, startX + mBitmap.getWidth(),
startY + mBitmap.getHeight()),
mBitmap,
- result -> getImageView().invalidate(),
+ result -> {
+ getImageView().invalidate();
+ mPrevStartCoordsOnScreen.x = startX;
+ mPrevStartCoordsOnScreen.y = startY;
+ },
mPixelCopyHandler);
} else {
Log.d(LOG_TAG, "Could not perform PixelCopy request");
diff --git a/com/android/keyguard/CarrierText.java b/com/android/keyguard/CarrierText.java
index 159ac4cc..13c48d0d 100644
--- a/com/android/keyguard/CarrierText.java
+++ b/com/android/keyguard/CarrierText.java
@@ -39,6 +39,7 @@ import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.IccCardConstants.State;
import com.android.internal.telephony.TelephonyIntents;
import com.android.settingslib.WirelessUtils;
+import android.telephony.TelephonyManager;
public class CarrierText extends TextView {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -52,6 +53,8 @@ public class CarrierText extends TextView {
private WifiManager mWifiManager;
+ private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()];
+
private KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onRefreshCarrierInfo() {
@@ -65,6 +68,22 @@ public class CarrierText extends TextView {
public void onStartedWakingUp() {
setSelected(true);
};
+
+ public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
+ if (slotId < 0) {
+ Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId);
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG,"onSimStateChanged: " + getStatusForIccState(simState));
+ if (getStatusForIccState(simState) == StatusMode.SimIoError) {
+ mSimErrorState[slotId] = true;
+ updateCarrierText();
+ } else if (mSimErrorState[slotId]) {
+ mSimErrorState[slotId] = false;
+ updateCarrierText();
+ }
+ };
};
/**
* The status of this lock screen. Primarily used for widgets on LockScreen.
@@ -77,7 +96,8 @@ public class CarrierText extends TextView {
SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
SimLocked, // SIM card is currently locked
SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
- SimNotReady; // SIM is not ready yet. May never be on devices w/o a SIM.
+ SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
+ SimIoError; // SIM card is faulty
}
public CarrierText(Context context) {
@@ -101,6 +121,35 @@ public class CarrierText extends TextView {
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
}
+ /**
+ * Checks if there are faulty cards. Adds the text depending on the slot of the card
+ * @param text: current carrier text based on the sim state
+ * @param noSims: whether a valid sim card is inserted
+ * @return text
+ */
+ private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) {
+ final CharSequence carrier = "";
+ CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
+ IccCardConstants.State.CARD_IO_ERROR, carrier);
+ for (int index = 0; index < mSimErrorState.length; index++) {
+ if (mSimErrorState[index]) {
+ // In the case when no sim cards are detected but a faulty card is inserted
+ // overwrite the text and only show "Invalid card"
+ if (noSims) {
+ return concatenate(carrierTextForSimIOError,
+ getContext().getText(com.android.internal.R.string.emergency_calls_only));
+ } else if (index == 0) {
+ // prepend "Invalid card" when faulty card is inserted in slot 0
+ text = concatenate(carrierTextForSimIOError, text);
+ } else {
+ // concatenate "Invalid card" when faulty card is inserted in slot 1
+ text = concatenate(text, carrierTextForSimIOError);
+ }
+ }
+ }
+ return text;
+ }
+
protected void updateCarrierText() {
boolean allSimsMissing = true;
boolean anySimReadyAndInService = false;
@@ -179,6 +228,7 @@ public class CarrierText extends TextView {
}
}
+ displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing);
// APM (airplane mode) != no carrier state. There are carrier services
// (e.g. WFC = Wi-Fi calling) which may operate in APM.
if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
@@ -270,6 +320,11 @@ public class CarrierText extends TextView {
getContext().getText(R.string.keyguard_sim_puk_locked_message),
text);
break;
+ case SimIoError:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.keyguard_sim_error_message_short),
+ text);
+ break;
}
return carrierText;
@@ -319,6 +374,8 @@ public class CarrierText extends TextView {
return StatusMode.SimPermDisabled;
case UNKNOWN:
return StatusMode.SimMissing;
+ case CARD_IO_ERROR:
+ return StatusMode.SimIoError;
}
return StatusMode.SimMissing;
}
diff --git a/com/android/keyguard/KeyguardSecurityModel.java b/com/android/keyguard/KeyguardSecurityModel.java
index 7baa57e7..0cb64230 100644
--- a/com/android/keyguard/KeyguardSecurityModel.java
+++ b/com/android/keyguard/KeyguardSecurityModel.java
@@ -57,16 +57,16 @@ public class KeyguardSecurityModel {
SecurityMode getSecurityMode(int userId) {
KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
- if (SubscriptionManager.isValidSubscriptionId(
- monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
- return SecurityMode.SimPin;
- }
-
if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) {
return SecurityMode.SimPuk;
}
+ if (SubscriptionManager.isValidSubscriptionId(
+ monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
+ return SecurityMode.SimPin;
+ }
+
final int security = mLockPatternUtils.getActivePasswordQuality(userId);
switch (security) {
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
diff --git a/com/android/keyguard/KeyguardUpdateMonitor.java b/com/android/keyguard/KeyguardUpdateMonitor.java
index d83a6c60..2bb992c4 100644
--- a/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -52,7 +52,6 @@ import android.media.AudioManager;
import android.os.BatteryManager;
import android.os.CancellationSignal;
import android.os.Handler;
-import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.Message;
import android.os.RemoteException;
@@ -78,7 +77,7 @@ import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
import com.google.android.collect.Lists;
@@ -902,6 +901,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
} else if (IccCardConstants.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
state = IccCardConstants.State.NETWORK_LOCKED;
+ } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
+ state = IccCardConstants.State.CARD_IO_ERROR;
} else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(stateExtra)
|| IccCardConstants.INTENT_VALUE_ICC_IMSI.equals(stateExtra)) {
// This is required because telephony doesn't return to "READY" after
@@ -1771,7 +1772,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
}
- private final TaskStackListener mTaskStackListener = new TaskStackListener() {
+ private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChangedBackground() {
try {
diff --git a/com/android/layoutlib/bridge/Bridge.java b/com/android/layoutlib/bridge/Bridge.java
index 0cfc1811..5dca8e7f 100644
--- a/com/android/layoutlib/bridge/Bridge.java
+++ b/com/android/layoutlib/bridge/Bridge.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,62 +14,659 @@
* limitations under the License.
*/
-package com.android.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession;
+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;
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 java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
+import libcore.io.MemoryMappedFile_Delegate;
+
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
/**
- * Legacy Bridge used in the SDK version of layoutlib
+ * 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)}
*/
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 class BridgeRenderSession extends RenderSession {
+ private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left";
- @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 static class StaticMethodNotImplementedException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
- return sImage;
+ public StaticMethodNotImplementedException(String msg) {
+ super(msg);
}
+ }
+
+ /**
+ * 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 Result render(long timeout, boolean forceMeasure) {
- return NOT_SUPPORTED_RESULT;
+ public void error(String tag, String message, Object data) {
+ System.err.println(message);
}
@Override
- public Result measure(long timeout) {
- return NOT_SUPPORTED_RESULT;
+ public void error(String tag, String message, Throwable throwable, Object data) {
+ System.err.println(message);
}
@Override
- public Result getResult() {
- return NOT_SUPPORTED_RESULT;
+ 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;
}
+
+ 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) {
- return new BridgeRenderSession();
+ 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 int getApiLevel() {
- return 0;
+ 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);
+ }
+ }
+
+ @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));
+ }
}
}
diff --git a/com/android/server/AppOpsService.java b/com/android/server/AppOpsService.java
index 50b8df2a..4ffa5f1f 100644
--- a/com/android/server/AppOpsService.java
+++ b/com/android/server/AppOpsService.java
@@ -491,7 +491,8 @@ public class AppOpsService extends IAppOpsService.Stub {
return Collections.emptyList();
}
synchronized (this) {
- Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false);
+ Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false /* edit */,
+ false /* uidMismatchExpected */);
if (pkgOps == null) {
return null;
}
@@ -530,7 +531,8 @@ public class AppOpsService extends IAppOpsService.Stub {
private void pruneOp(Op op, int uid, String packageName) {
if (op.time == 0 && op.rejectTime == 0) {
- Ops ops = getOpsRawLocked(uid, packageName, false);
+ Ops ops = getOpsRawLocked(uid, packageName, false /* edit */,
+ false /* uidMismatchExpected */);
if (ops != null) {
ops.remove(op.op);
if (ops.size() <= 0) {
@@ -1046,7 +1048,9 @@ public class AppOpsService extends IAppOpsService.Stub {
public int checkPackage(int uid, String packageName) {
Preconditions.checkNotNull(packageName);
synchronized (this) {
- if (getOpsRawLocked(uid, packageName, true) != null) {
+ Ops ops = getOpsRawLocked(uid, packageName, true /* edit */,
+ true /* uidMismatchExpected */);
+ if (ops != null) {
return AppOpsManager.MODE_ALLOWED;
} else {
return AppOpsManager.MODE_ERRORED;
@@ -1090,7 +1094,8 @@ public class AppOpsService extends IAppOpsService.Stub {
private int noteOperationUnchecked(int code, int uid, String packageName,
int proxyUid, String proxyPackageName) {
synchronized (this) {
- Ops ops = getOpsRawLocked(uid, packageName, true);
+ Ops ops = getOpsRawLocked(uid, packageName, true /* edit */,
+ false /* uidMismatchExpected */);
if (ops == null) {
if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ " package " + packageName);
@@ -1148,7 +1153,8 @@ public class AppOpsService extends IAppOpsService.Stub {
}
ClientState client = (ClientState)token;
synchronized (this) {
- Ops ops = getOpsRawLocked(uid, resolvedPackageName, true);
+ Ops ops = getOpsRawLocked(uid, resolvedPackageName, true /* edit */,
+ false /* uidMismatchExpected */);
if (ops == null) {
if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ " package " + resolvedPackageName);
@@ -1274,7 +1280,8 @@ public class AppOpsService extends IAppOpsService.Stub {
return uidState;
}
- private Ops getOpsRawLocked(int uid, String packageName, boolean edit) {
+ private Ops getOpsRawLocked(int uid, String packageName, boolean edit,
+ boolean uidMismatchExpected) {
UidState uidState = getUidStateLocked(uid, edit);
if (uidState == null) {
return null;
@@ -1326,10 +1333,12 @@ public class AppOpsService extends IAppOpsService.Stub {
if (pkgUid != uid) {
// Oops! The package name is not valid for the uid they are calling
// under. Abort.
- RuntimeException ex = new RuntimeException("here");
- ex.fillInStackTrace();
- Slog.w(TAG, "Bad call: specified package " + packageName
- + " under uid " + uid + " but it is really " + pkgUid, ex);
+ if (!uidMismatchExpected) {
+ RuntimeException ex = new RuntimeException("here");
+ ex.fillInStackTrace();
+ Slog.w(TAG, "Bad call: specified package " + packageName
+ + " under uid " + uid + " but it is really " + pkgUid, ex);
+ }
return null;
}
} finally {
@@ -1359,7 +1368,8 @@ public class AppOpsService extends IAppOpsService.Stub {
}
private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
- Ops ops = getOpsRawLocked(uid, packageName, edit);
+ Ops ops = getOpsRawLocked(uid, packageName, edit,
+ false /* uidMismatchExpected */);
if (ops == null) {
return null;
}
@@ -1393,7 +1403,8 @@ public class AppOpsService extends IAppOpsService.Stub {
if (AppOpsManager.opAllowSystemBypassRestriction(code)) {
// If we are the system, bypass user restrictions for certain codes
synchronized (this) {
- Ops ops = getOpsRawLocked(uid, packageName, true);
+ Ops ops = getOpsRawLocked(uid, packageName, true /* edit */,
+ false /* uidMismatchExpected */);
if ((ops != null) && ops.isPrivileged) {
return false;
}
@@ -1713,7 +1724,8 @@ public class AppOpsService extends IAppOpsService.Stub {
out.startTag(null, "uid");
out.attribute(null, "n", Integer.toString(pkg.getUid()));
synchronized (this) {
- Ops ops = getOpsRawLocked(pkg.getUid(), pkg.getPackageName(), false);
+ Ops ops = getOpsRawLocked(pkg.getUid(), pkg.getPackageName(),
+ false /* edit */, false /* uidMismatchExpected */);
// Should always be present as the list of PackageOps is generated
// from Ops.
if (ops != null) {
diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java
index 5106c8d7..47be0a70 100644
--- a/com/android/server/BatteryService.java
+++ b/com/android/server/BatteryService.java
@@ -24,6 +24,7 @@ import android.os.PowerManager;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCommand;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
import com.android.server.am.BatteryStatsService;
@@ -35,6 +36,10 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.hardware.health.V2_0.HealthInfo;
+import android.hardware.health.V2_0.IHealth;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.BatteryProperties;
@@ -62,6 +67,9 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.NoSuchElementException;
/**
* <p>BatteryService monitors the charging status, and charge level of the device
@@ -118,8 +126,8 @@ public final class BatteryService extends SystemService {
private final Object mLock = new Object();
- private BatteryProperties mBatteryProps;
- private final BatteryProperties mLastBatteryProps = new BatteryProperties();
+ private HealthInfo mHealthInfo;
+ private final HealthInfo mLastHealthInfo = new HealthInfo();
private boolean mBatteryLevelCritical;
private int mLastBatteryStatus;
private int mLastBatteryHealth;
@@ -251,16 +259,16 @@ public final class BatteryService extends SystemService {
private boolean isPoweredLocked(int plugTypeSet) {
// assume we are powered if battery state is unknown so
// the "stay on while plugged in" option will work.
- if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+ if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
return true;
}
- if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mBatteryProps.chargerAcOnline) {
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.legacy.chargerAcOnline) {
return true;
}
- if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mBatteryProps.chargerUsbOnline) {
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.legacy.chargerUsbOnline) {
return true;
}
- if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mBatteryProps.chargerWirelessOnline) {
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.legacy.chargerWirelessOnline) {
return true;
}
return false;
@@ -277,15 +285,15 @@ public final class BatteryService extends SystemService {
* (becomes <= mLowBatteryWarningLevel).
*/
return !plugged
- && mBatteryProps.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
- && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel
+ && mHealthInfo.legacy.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+ && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel
&& (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
}
private void shutdownIfNoPowerLocked() {
// shut down gracefully if our battery is critically low and we are not powered.
// wait until the system has booted before attempting to display the shutdown dialog.
- if (mBatteryProps.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
+ if (mHealthInfo.legacy.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -306,7 +314,7 @@ public final class BatteryService extends SystemService {
// shut down gracefully if temperature is too high (> 68.0C by default)
// wait until the system has booted before attempting to display the
// shutdown dialog.
- if (mBatteryProps.batteryTemperature > mShutdownBatteryTemperature) {
+ if (mHealthInfo.legacy.batteryTemperature > mShutdownBatteryTemperature) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -326,25 +334,66 @@ public final class BatteryService extends SystemService {
private void update(BatteryProperties props) {
synchronized (mLock) {
if (!mUpdatesStopped) {
- mBatteryProps = props;
+ mHealthInfo = new HealthInfo();
+ copy(mHealthInfo, props);
// Process the new values.
processValuesLocked(false);
} else {
- mLastBatteryProps.set(props);
+ copy(mLastHealthInfo, props);
}
}
}
+ private static void copy(HealthInfo dst, HealthInfo src) {
+ dst.legacy.chargerAcOnline = src.legacy.chargerAcOnline;
+ dst.legacy.chargerUsbOnline = src.legacy.chargerUsbOnline;
+ dst.legacy.chargerWirelessOnline = src.legacy.chargerWirelessOnline;
+ dst.legacy.maxChargingCurrent = src.legacy.maxChargingCurrent;
+ dst.legacy.maxChargingVoltage = src.legacy.maxChargingVoltage;
+ dst.legacy.batteryStatus = src.legacy.batteryStatus;
+ dst.legacy.batteryHealth = src.legacy.batteryHealth;
+ dst.legacy.batteryPresent = src.legacy.batteryPresent;
+ dst.legacy.batteryLevel = src.legacy.batteryLevel;
+ dst.legacy.batteryVoltage = src.legacy.batteryVoltage;
+ dst.legacy.batteryTemperature = src.legacy.batteryTemperature;
+ dst.legacy.batteryCurrent = src.legacy.batteryCurrent;
+ dst.legacy.batteryCycleCount = src.legacy.batteryCycleCount;
+ dst.legacy.batteryFullCharge = src.legacy.batteryFullCharge;
+ dst.legacy.batteryChargeCounter = src.legacy.batteryChargeCounter;
+ dst.legacy.batteryTechnology = src.legacy.batteryTechnology;
+ dst.batteryCurrentAverage = src.batteryCurrentAverage;
+ dst.batteryCapacity = src.batteryCapacity;
+ dst.energyCounter = src.energyCounter;
+ }
+
+ // TODO(b/62229583): remove this function when BatteryProperties are completely replaced.
+ private static void copy(HealthInfo dst, BatteryProperties src) {
+ dst.legacy.chargerAcOnline = src.chargerAcOnline;
+ dst.legacy.chargerUsbOnline = src.chargerUsbOnline;
+ dst.legacy.chargerWirelessOnline = src.chargerWirelessOnline;
+ dst.legacy.maxChargingCurrent = src.maxChargingCurrent;
+ dst.legacy.maxChargingVoltage = src.maxChargingVoltage;
+ dst.legacy.batteryStatus = src.batteryStatus;
+ dst.legacy.batteryHealth = src.batteryHealth;
+ dst.legacy.batteryPresent = src.batteryPresent;
+ dst.legacy.batteryLevel = src.batteryLevel;
+ dst.legacy.batteryVoltage = src.batteryVoltage;
+ dst.legacy.batteryTemperature = src.batteryTemperature;
+ dst.legacy.batteryFullCharge = src.batteryFullCharge;
+ dst.legacy.batteryChargeCounter = src.batteryChargeCounter;
+ dst.legacy.batteryTechnology = src.batteryTechnology;
+ }
+
private void processValuesLocked(boolean force) {
boolean logOutlier = false;
long dischargeDuration = 0;
- mBatteryLevelCritical = (mBatteryProps.batteryLevel <= mCriticalBatteryLevel);
- if (mBatteryProps.chargerAcOnline) {
+ mBatteryLevelCritical = (mHealthInfo.legacy.batteryLevel <= mCriticalBatteryLevel);
+ if (mHealthInfo.legacy.chargerAcOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
- } else if (mBatteryProps.chargerUsbOnline) {
+ } else if (mHealthInfo.legacy.chargerUsbOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
- } else if (mBatteryProps.chargerWirelessOnline) {
+ } else if (mHealthInfo.legacy.chargerWirelessOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
} else {
mPlugType = BATTERY_PLUGGED_NONE;
@@ -352,30 +401,17 @@ public final class BatteryService extends SystemService {
if (DEBUG) {
Slog.d(TAG, "Processing new values: "
- + "chargerAcOnline=" + mBatteryProps.chargerAcOnline
- + ", chargerUsbOnline=" + mBatteryProps.chargerUsbOnline
- + ", chargerWirelessOnline=" + mBatteryProps.chargerWirelessOnline
- + ", maxChargingCurrent" + mBatteryProps.maxChargingCurrent
- + ", maxChargingVoltage" + mBatteryProps.maxChargingVoltage
- + ", batteryStatus=" + mBatteryProps.batteryStatus
- + ", batteryHealth=" + mBatteryProps.batteryHealth
- + ", batteryPresent=" + mBatteryProps.batteryPresent
- + ", batteryLevel=" + mBatteryProps.batteryLevel
- + ", batteryTechnology=" + mBatteryProps.batteryTechnology
- + ", batteryVoltage=" + mBatteryProps.batteryVoltage
- + ", batteryChargeCounter=" + mBatteryProps.batteryChargeCounter
- + ", batteryFullCharge=" + mBatteryProps.batteryFullCharge
- + ", batteryTemperature=" + mBatteryProps.batteryTemperature
+ + "info=" + mHealthInfo
+ ", mBatteryLevelCritical=" + mBatteryLevelCritical
+ ", mPlugType=" + mPlugType);
}
// Let the battery stats keep track of the current level.
try {
- mBatteryStats.setBatteryState(mBatteryProps.batteryStatus, mBatteryProps.batteryHealth,
- mPlugType, mBatteryProps.batteryLevel, mBatteryProps.batteryTemperature,
- mBatteryProps.batteryVoltage, mBatteryProps.batteryChargeCounter,
- mBatteryProps.batteryFullCharge);
+ mBatteryStats.setBatteryState(mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth,
+ mPlugType, mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryTemperature,
+ mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryChargeCounter,
+ mHealthInfo.legacy.batteryFullCharge);
} catch (RemoteException e) {
// Should never happen.
}
@@ -383,16 +419,16 @@ public final class BatteryService extends SystemService {
shutdownIfNoPowerLocked();
shutdownIfOverTempLocked();
- if (force || (mBatteryProps.batteryStatus != mLastBatteryStatus ||
- mBatteryProps.batteryHealth != mLastBatteryHealth ||
- mBatteryProps.batteryPresent != mLastBatteryPresent ||
- mBatteryProps.batteryLevel != mLastBatteryLevel ||
+ if (force || (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
+ mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
+ mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
+ mHealthInfo.legacy.batteryLevel != mLastBatteryLevel ||
mPlugType != mLastPlugType ||
- mBatteryProps.batteryVoltage != mLastBatteryVoltage ||
- mBatteryProps.batteryTemperature != mLastBatteryTemperature ||
- mBatteryProps.maxChargingCurrent != mLastMaxChargingCurrent ||
- mBatteryProps.maxChargingVoltage != mLastMaxChargingVoltage ||
- mBatteryProps.batteryChargeCounter != mLastChargeCounter ||
+ mHealthInfo.legacy.batteryVoltage != mLastBatteryVoltage ||
+ mHealthInfo.legacy.batteryTemperature != mLastBatteryTemperature ||
+ mHealthInfo.legacy.maxChargingCurrent != mLastMaxChargingCurrent ||
+ mHealthInfo.legacy.maxChargingVoltage != mLastMaxChargingVoltage ||
+ mHealthInfo.legacy.batteryChargeCounter != mLastChargeCounter ||
mInvalidCharger != mLastInvalidCharger)) {
if (mPlugType != mLastPlugType) {
@@ -401,33 +437,33 @@ public final class BatteryService extends SystemService {
// There's no value in this data unless we've discharged at least once and the
// battery level has changed; so don't log until it does.
- if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryProps.batteryLevel) {
+ if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.legacy.batteryLevel) {
dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
logOutlier = true;
EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
- mDischargeStartLevel, mBatteryProps.batteryLevel);
+ mDischargeStartLevel, mHealthInfo.legacy.batteryLevel);
// make sure we see a discharge event before logging again
mDischargeStartTime = 0;
}
} else if (mPlugType == BATTERY_PLUGGED_NONE) {
// charging -> discharging or we just powered up
mDischargeStartTime = SystemClock.elapsedRealtime();
- mDischargeStartLevel = mBatteryProps.batteryLevel;
+ mDischargeStartLevel = mHealthInfo.legacy.batteryLevel;
}
}
- if (mBatteryProps.batteryStatus != mLastBatteryStatus ||
- mBatteryProps.batteryHealth != mLastBatteryHealth ||
- mBatteryProps.batteryPresent != mLastBatteryPresent ||
+ if (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
+ mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
+ mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
mPlugType != mLastPlugType) {
EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
- mBatteryProps.batteryStatus, mBatteryProps.batteryHealth, mBatteryProps.batteryPresent ? 1 : 0,
- mPlugType, mBatteryProps.batteryTechnology);
+ mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth, mHealthInfo.legacy.batteryPresent ? 1 : 0,
+ mPlugType, mHealthInfo.legacy.batteryTechnology);
}
- if (mBatteryProps.batteryLevel != mLastBatteryLevel) {
+ if (mHealthInfo.legacy.batteryLevel != mLastBatteryLevel) {
// Don't do this just from voltage or temperature changes, that is
// too noisy.
EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
- mBatteryProps.batteryLevel, mBatteryProps.batteryVoltage, mBatteryProps.batteryTemperature);
+ mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryTemperature);
}
if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
mPlugType == BATTERY_PLUGGED_NONE) {
@@ -440,16 +476,16 @@ public final class BatteryService extends SystemService {
if (!mBatteryLevelLow) {
// Should we now switch in to low battery mode?
if (mPlugType == BATTERY_PLUGGED_NONE
- && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel) {
+ && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel) {
mBatteryLevelLow = true;
}
} else {
// Should we now switch out of low battery mode?
if (mPlugType != BATTERY_PLUGGED_NONE) {
mBatteryLevelLow = false;
- } else if (mBatteryProps.batteryLevel >= mLowBatteryCloseWarningLevel) {
+ } else if (mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) {
mBatteryLevelLow = false;
- } else if (force && mBatteryProps.batteryLevel >= mLowBatteryWarningLevel) {
+ } else if (force && mHealthInfo.legacy.batteryLevel >= mLowBatteryWarningLevel) {
// If being forced, the previous state doesn't matter, we will just
// absolutely check to see if we are now above the warning level.
mBatteryLevelLow = false;
@@ -496,7 +532,7 @@ public final class BatteryService extends SystemService {
}
});
} else if (mSentLowBatteryBroadcast &&
- mBatteryProps.batteryLevel >= mLowBatteryCloseWarningLevel) {
+ mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) {
mSentLowBatteryBroadcast = false;
final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -522,16 +558,16 @@ public final class BatteryService extends SystemService {
logOutlierLocked(dischargeDuration);
}
- mLastBatteryStatus = mBatteryProps.batteryStatus;
- mLastBatteryHealth = mBatteryProps.batteryHealth;
- mLastBatteryPresent = mBatteryProps.batteryPresent;
- mLastBatteryLevel = mBatteryProps.batteryLevel;
+ mLastBatteryStatus = mHealthInfo.legacy.batteryStatus;
+ mLastBatteryHealth = mHealthInfo.legacy.batteryHealth;
+ mLastBatteryPresent = mHealthInfo.legacy.batteryPresent;
+ mLastBatteryLevel = mHealthInfo.legacy.batteryLevel;
mLastPlugType = mPlugType;
- mLastBatteryVoltage = mBatteryProps.batteryVoltage;
- mLastBatteryTemperature = mBatteryProps.batteryTemperature;
- mLastMaxChargingCurrent = mBatteryProps.maxChargingCurrent;
- mLastMaxChargingVoltage = mBatteryProps.maxChargingVoltage;
- mLastChargeCounter = mBatteryProps.batteryChargeCounter;
+ mLastBatteryVoltage = mHealthInfo.legacy.batteryVoltage;
+ mLastBatteryTemperature = mHealthInfo.legacy.batteryTemperature;
+ mLastMaxChargingCurrent = mHealthInfo.legacy.maxChargingCurrent;
+ mLastMaxChargingVoltage = mHealthInfo.legacy.maxChargingVoltage;
+ mLastChargeCounter = mHealthInfo.legacy.batteryChargeCounter;
mLastBatteryLevelCritical = mBatteryLevelCritical;
mLastInvalidCharger = mInvalidCharger;
}
@@ -543,38 +579,26 @@ public final class BatteryService extends SystemService {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_REPLACE_PENDING);
- int icon = getIconLocked(mBatteryProps.batteryLevel);
+ int icon = getIconLocked(mHealthInfo.legacy.batteryLevel);
intent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
- intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryProps.batteryStatus);
- intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryProps.batteryHealth);
- intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryProps.batteryPresent);
- intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryProps.batteryLevel);
+ intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.legacy.batteryStatus);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.legacy.batteryHealth);
+ intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.legacy.batteryPresent);
+ intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.legacy.batteryLevel);
intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
- intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryProps.batteryVoltage);
- intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryProps.batteryTemperature);
- intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryProps.batteryTechnology);
+ intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.legacy.batteryVoltage);
+ intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
+ intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
- intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mBatteryProps.maxChargingCurrent);
- intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mBatteryProps.maxChargingVoltage);
- intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mBatteryProps.batteryChargeCounter);
+ intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
+ intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
+ intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
if (DEBUG) {
- Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. level:" + mBatteryProps.batteryLevel +
- ", scale:" + BATTERY_SCALE + ", status:" + mBatteryProps.batteryStatus +
- ", health:" + mBatteryProps.batteryHealth +
- ", present:" + mBatteryProps.batteryPresent +
- ", voltage: " + mBatteryProps.batteryVoltage +
- ", temperature: " + mBatteryProps.batteryTemperature +
- ", technology: " + mBatteryProps.batteryTechnology +
- ", AC powered:" + mBatteryProps.chargerAcOnline +
- ", USB powered:" + mBatteryProps.chargerUsbOnline +
- ", Wireless powered:" + mBatteryProps.chargerWirelessOnline +
- ", icon:" + icon + ", invalid charger:" + mInvalidCharger +
- ", maxChargingCurrent:" + mBatteryProps.maxChargingCurrent +
- ", maxChargingVoltage:" + mBatteryProps.maxChargingVoltage +
- ", chargeCounter:" + mBatteryProps.batteryChargeCounter);
+ Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE
+ + ", info:" + mHealthInfo.toString());
}
mHandler.post(new Runnable() {
@@ -635,14 +659,14 @@ public final class BatteryService extends SystemService {
long durationThreshold = Long.parseLong(durationThresholdString);
int dischargeThreshold = Integer.parseInt(dischargeThresholdString);
if (duration <= durationThreshold &&
- mDischargeStartLevel - mBatteryProps.batteryLevel >= dischargeThreshold) {
+ mDischargeStartLevel - mHealthInfo.legacy.batteryLevel >= dischargeThreshold) {
// If the discharge cycle is bad enough we want to know about it.
logBatteryStatsLocked();
}
if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold +
" discharge threshold: " + dischargeThreshold);
if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " +
- (mDischargeStartLevel - mBatteryProps.batteryLevel));
+ (mDischargeStartLevel - mHealthInfo.legacy.batteryLevel));
} catch (NumberFormatException e) {
Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
durationThresholdString + " or " + dischargeThresholdString);
@@ -651,14 +675,14 @@ public final class BatteryService extends SystemService {
}
private int getIconLocked(int level) {
- if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
+ if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
return com.android.internal.R.drawable.stat_sys_battery_charge;
- } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+ } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
return com.android.internal.R.drawable.stat_sys_battery;
- } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
- || mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
+ } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
+ || mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
- && mBatteryProps.batteryLevel >= 100) {
+ && mHealthInfo.legacy.batteryLevel >= 100) {
return com.android.internal.R.drawable.stat_sys_battery_charge;
} else {
return com.android.internal.R.drawable.stat_sys_battery;
@@ -720,11 +744,11 @@ public final class BatteryService extends SystemService {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
if (!mUpdatesStopped) {
- mLastBatteryProps.set(mBatteryProps);
+ copy(mLastHealthInfo, mHealthInfo);
}
- mBatteryProps.chargerAcOnline = false;
- mBatteryProps.chargerUsbOnline = false;
- mBatteryProps.chargerWirelessOnline = false;
+ mHealthInfo.legacy.chargerAcOnline = false;
+ mHealthInfo.legacy.chargerUsbOnline = false;
+ mHealthInfo.legacy.chargerWirelessOnline = false;
long ident = Binder.clearCallingIdentity();
try {
mUpdatesStopped = true;
@@ -751,30 +775,30 @@ public final class BatteryService extends SystemService {
}
try {
if (!mUpdatesStopped) {
- mLastBatteryProps.set(mBatteryProps);
+ copy(mLastHealthInfo, mHealthInfo);
}
boolean update = true;
switch (key) {
case "present":
- mBatteryProps.batteryPresent = Integer.parseInt(value) != 0;
+ mHealthInfo.legacy.batteryPresent = Integer.parseInt(value) != 0;
break;
case "ac":
- mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0;
+ mHealthInfo.legacy.chargerAcOnline = Integer.parseInt(value) != 0;
break;
case "usb":
- mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0;
+ mHealthInfo.legacy.chargerUsbOnline = Integer.parseInt(value) != 0;
break;
case "wireless":
- mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0;
+ mHealthInfo.legacy.chargerWirelessOnline = Integer.parseInt(value) != 0;
break;
case "status":
- mBatteryProps.batteryStatus = Integer.parseInt(value);
+ mHealthInfo.legacy.batteryStatus = Integer.parseInt(value);
break;
case "level":
- mBatteryProps.batteryLevel = Integer.parseInt(value);
+ mHealthInfo.legacy.batteryLevel = Integer.parseInt(value);
break;
case "temp":
- mBatteryProps.batteryTemperature = Integer.parseInt(value);
+ mHealthInfo.legacy.batteryTemperature = Integer.parseInt(value);
break;
case "invalid":
mInvalidCharger = Integer.parseInt(value);
@@ -806,7 +830,7 @@ public final class BatteryService extends SystemService {
try {
if (mUpdatesStopped) {
mUpdatesStopped = false;
- mBatteryProps.set(mLastBatteryProps);
+ copy(mHealthInfo, mLastHealthInfo);
processValuesFromShellLocked(pw, opts);
}
} finally {
@@ -833,20 +857,20 @@ public final class BatteryService extends SystemService {
if (mUpdatesStopped) {
pw.println(" (UPDATES STOPPED -- use 'reset' to restart)");
}
- pw.println(" AC powered: " + mBatteryProps.chargerAcOnline);
- pw.println(" USB powered: " + mBatteryProps.chargerUsbOnline);
- pw.println(" Wireless powered: " + mBatteryProps.chargerWirelessOnline);
- pw.println(" Max charging current: " + mBatteryProps.maxChargingCurrent);
- pw.println(" Max charging voltage: " + mBatteryProps.maxChargingVoltage);
- pw.println(" Charge counter: " + mBatteryProps.batteryChargeCounter);
- pw.println(" status: " + mBatteryProps.batteryStatus);
- pw.println(" health: " + mBatteryProps.batteryHealth);
- pw.println(" present: " + mBatteryProps.batteryPresent);
- pw.println(" level: " + mBatteryProps.batteryLevel);
+ pw.println(" AC powered: " + mHealthInfo.legacy.chargerAcOnline);
+ pw.println(" USB powered: " + mHealthInfo.legacy.chargerUsbOnline);
+ pw.println(" Wireless powered: " + mHealthInfo.legacy.chargerWirelessOnline);
+ pw.println(" Max charging current: " + mHealthInfo.legacy.maxChargingCurrent);
+ pw.println(" Max charging voltage: " + mHealthInfo.legacy.maxChargingVoltage);
+ pw.println(" Charge counter: " + mHealthInfo.legacy.batteryChargeCounter);
+ pw.println(" status: " + mHealthInfo.legacy.batteryStatus);
+ pw.println(" health: " + mHealthInfo.legacy.batteryHealth);
+ pw.println(" present: " + mHealthInfo.legacy.batteryPresent);
+ pw.println(" level: " + mHealthInfo.legacy.batteryLevel);
pw.println(" scale: " + BATTERY_SCALE);
- pw.println(" voltage: " + mBatteryProps.batteryVoltage);
- pw.println(" temperature: " + mBatteryProps.batteryTemperature);
- pw.println(" technology: " + mBatteryProps.batteryTechnology);
+ pw.println(" voltage: " + mHealthInfo.legacy.batteryVoltage);
+ pw.println(" temperature: " + mHealthInfo.legacy.batteryTemperature);
+ pw.println(" technology: " + mHealthInfo.legacy.batteryTechnology);
} else {
Shell shell = new Shell();
shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -860,25 +884,25 @@ public final class BatteryService extends SystemService {
synchronized (mLock) {
proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped);
int batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_NONE;
- if (mBatteryProps.chargerAcOnline) {
+ if (mHealthInfo.legacy.chargerAcOnline) {
batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_AC;
- } else if (mBatteryProps.chargerUsbOnline) {
+ } else if (mHealthInfo.legacy.chargerUsbOnline) {
batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_USB;
- } else if (mBatteryProps.chargerWirelessOnline) {
+ } else if (mHealthInfo.legacy.chargerWirelessOnline) {
batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_WIRELESS;
}
proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue);
- proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mBatteryProps.maxChargingCurrent);
- proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mBatteryProps.maxChargingVoltage);
- proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mBatteryProps.batteryChargeCounter);
- proto.write(BatteryServiceDumpProto.STATUS, mBatteryProps.batteryStatus);
- proto.write(BatteryServiceDumpProto.HEALTH, mBatteryProps.batteryHealth);
- proto.write(BatteryServiceDumpProto.IS_PRESENT, mBatteryProps.batteryPresent);
- proto.write(BatteryServiceDumpProto.LEVEL, mBatteryProps.batteryLevel);
+ proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
+ proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
+ proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
+ proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.legacy.batteryStatus);
+ proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.legacy.batteryHealth);
+ proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.legacy.batteryPresent);
+ proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.legacy.batteryLevel);
proto.write(BatteryServiceDumpProto.SCALE, BATTERY_SCALE);
- proto.write(BatteryServiceDumpProto.VOLTAGE, mBatteryProps.batteryVoltage);
- proto.write(BatteryServiceDumpProto.TEMPERATURE, mBatteryProps.batteryTemperature);
- proto.write(BatteryServiceDumpProto.TECHNOLOGY, mBatteryProps.batteryTechnology);
+ proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.legacy.batteryVoltage);
+ proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
+ proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
}
proto.flush();
}
@@ -911,8 +935,8 @@ public final class BatteryService extends SystemService {
* Synchronize on BatteryService.
*/
public void updateLightsLocked() {
- final int level = mBatteryProps.batteryLevel;
- final int status = mBatteryProps.batteryStatus;
+ final int level = mHealthInfo.legacy.batteryLevel;
+ final int status = mHealthInfo.legacy.batteryStatus;
if (level < mLowBatteryWarningLevel) {
if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
// Solid red when battery is charging
@@ -985,7 +1009,7 @@ public final class BatteryService extends SystemService {
@Override
public int getBatteryLevel() {
synchronized (mLock) {
- return mBatteryProps.batteryLevel;
+ return mHealthInfo.legacy.batteryLevel;
}
}
@@ -1003,4 +1027,121 @@ public final class BatteryService extends SystemService {
}
}
}
+
+ /**
+ * HealthServiceWrapper wraps the internal IHealth service and refreshes the service when
+ * necessary.
+ *
+ * On new registration of IHealth service, {@link #onRegistration onRegistration} is called and
+ * the internal service is refreshed.
+ * On death of an existing IHealth service, the internal service is NOT cleared to avoid
+ * race condition between death notification and new service notification. Hence,
+ * a caller must check for transaction errors when calling into the service.
+ *
+ * @hide Should only be used internally.
+ */
+ @VisibleForTesting
+ static final class HealthServiceWrapper {
+ private static final String TAG = "HealthServiceWrapper";
+ public static final String INSTANCE_HEALTHD = "backup";
+ public static final String INSTANCE_VENDOR = "default";
+ // All interesting instances, sorted by priority high -> low.
+ private static final List<String> sAllInstances =
+ Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
+
+ private final IServiceNotification mNotification = new Notification();
+ private Callback mCallback;
+ private IHealthSupplier mHealthSupplier;
+
+ /**
+ * init should be called after constructor. For testing purposes, init is not called by
+ * constructor.
+ */
+ HealthServiceWrapper() {
+ }
+
+ /**
+ * Start monitoring registration of new IHealth services. Only instances that are in
+ * {@code sAllInstances} and in device / framework manifest are used. This function should
+ * only be called once.
+ * @throws RemoteException transaction error when talking to IServiceManager
+ * @throws NoSuchElementException if one of the following cases:
+ * - No service manager;
+ * - none of {@code sAllInstances} are in manifests (i.e. not
+ * available on this device), or none of these instances are available to current
+ * process.
+ * @throws NullPointerException when callback is null or supplier is null
+ */
+ void init(Callback callback,
+ IServiceManagerSupplier managerSupplier,
+ IHealthSupplier healthSupplier)
+ throws RemoteException, NoSuchElementException, NullPointerException {
+ if (callback == null || managerSupplier == null || healthSupplier == null)
+ throw new NullPointerException();
+
+ mCallback = callback;
+ mHealthSupplier = healthSupplier;
+
+ IServiceManager manager = managerSupplier.get();
+ for (String name : sAllInstances) {
+ if (manager.getTransport(IHealth.kInterfaceName, name) ==
+ IServiceManager.Transport.EMPTY) {
+ continue;
+ }
+
+ manager.registerForNotifications(IHealth.kInterfaceName, name, mNotification);
+ Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + name);
+ return;
+ }
+
+ throw new NoSuchElementException(String.format(
+ "No IHealth service instance among %s is available. Perhaps no permission?",
+ sAllInstances.toString()));
+ }
+
+ interface Callback {
+ /**
+ * This function is invoked asynchronously when a new and related IServiceNotification
+ * is received.
+ * @param service the recently retrieved service from IServiceManager.
+ * Can be a dead service before service notification of a new service is delivered.
+ * Implementation must handle cases for {@link RemoteException}s when calling
+ * into service.
+ * @param instance instance name.
+ */
+ void onRegistration(IHealth service, String instance);
+ }
+
+ /**
+ * Supplier of services.
+ * Must not return null; throw {@link NoSuchElementException} if a service is not available.
+ */
+ interface IServiceManagerSupplier {
+ IServiceManager get() throws NoSuchElementException, RemoteException;
+ }
+ /**
+ * Supplier of services.
+ * Must not return null; throw {@link NoSuchElementException} if a service is not available.
+ */
+ interface IHealthSupplier {
+ IHealth get(String instanceName) throws NoSuchElementException, RemoteException;
+ }
+
+ private class Notification extends IServiceNotification.Stub {
+ @Override
+ public final void onRegistration(String interfaceName, String instanceName,
+ boolean preexisting) {
+ if (!IHealth.kInterfaceName.equals(interfaceName)) return;
+ if (!sAllInstances.contains(instanceName)) return;
+ try {
+ IHealth service = mHealthSupplier.get(instanceName);
+ Slog.i(TAG, "health: new instance registered " + instanceName);
+ mCallback.onRegistration(service, instanceName);
+ } catch (NoSuchElementException | RemoteException ex) {
+ Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " +
+ ex.getMessage() + ". Perhaps no permission?");
+ }
+ }
+ }
+ }
}
diff --git a/com/android/server/IntentResolver.java b/com/android/server/IntentResolver.java
index 40499c96..119c9df6 100644
--- a/com/android/server/IntentResolver.java
+++ b/com/android/server/IntentResolver.java
@@ -38,6 +38,8 @@ import android.util.Printer;
import android.content.Intent;
import android.content.IntentFilter;
+import android.util.proto.ProtoOutputStream;
+
import com.android.internal.util.FastPrintWriter;
/**
@@ -279,6 +281,31 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
return printedSomething;
}
+ void writeProtoMap(ProtoOutputStream proto, long fieldId, ArrayMap<String, F[]> map) {
+ int N = map.size();
+ for (int mapi = 0; mapi < N; mapi++) {
+ long token = proto.start(fieldId);
+ proto.write(IntentResolverProto.ArrayMapEntry.KEY, map.keyAt(mapi));
+ for (F f : map.valueAt(mapi)) {
+ if (f != null) {
+ proto.write(IntentResolverProto.ArrayMapEntry.VALUES, f.toString());
+ }
+ }
+ proto.end(token);
+ }
+ }
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ writeProtoMap(proto, IntentResolverProto.FULL_MIME_TYPES, mTypeToFilter);
+ writeProtoMap(proto, IntentResolverProto.BASE_MIME_TYPES, mBaseTypeToFilter);
+ writeProtoMap(proto, IntentResolverProto.WILD_MIME_TYPES, mWildTypeToFilter);
+ writeProtoMap(proto, IntentResolverProto.SCHEMES, mSchemeToFilter);
+ writeProtoMap(proto, IntentResolverProto.NON_DATA_ACTIONS, mActionToFilter);
+ writeProtoMap(proto, IntentResolverProto.MIME_TYPED_ACTIONS, mTypedActionToFilter);
+ proto.end(token);
+ }
+
public boolean dump(PrintWriter out, String title, String prefix, String packageName,
boolean printFilter, boolean collapseDuplicates) {
String innerPrefix = prefix + " ";
diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java
index 92cbd3d5..49dd5285 100644
--- a/com/android/server/SystemServer.java
+++ b/com/android/server/SystemServer.java
@@ -125,6 +125,7 @@ import java.util.Timer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
+import static android.os.IServiceManager.DUMP_PRIORITY_CRITICAL;
import static android.view.Display.DEFAULT_DISPLAY;
public final class SystemServer {
@@ -824,7 +825,8 @@ public final class SystemServer {
wm = WindowManagerService.main(context, inputManager,
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore, new PhoneWindowManager());
- ServiceManager.addService(Context.WINDOW_SERVICE, wm);
+ ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
+ DUMP_PRIORITY_CRITICAL);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
traceEnd();
@@ -1640,11 +1642,7 @@ public final class SystemServer {
traceEnd();
traceBeginAndSlog("MakePackageManagerServiceReady");
- try {
- mPackageManagerService.systemReady();
- } catch (Throwable e) {
- reportWtf("making Package Manager Service ready", e);
- }
+ mPackageManagerService.systemReady();
traceEnd();
traceBeginAndSlog("MakeDisplayManagerServiceReady");
diff --git a/com/android/server/VibratorService.java b/com/android/server/VibratorService.java
index 046eb761..8b79b9dd 100644
--- a/com/android/server/VibratorService.java
+++ b/com/android/server/VibratorService.java
@@ -373,12 +373,24 @@ public class VibratorService extends IVibratorService.Stub
if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming())
&& newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
if (DEBUG) {
- Slog.e(TAG, "Ignoring incoming vibration in favor of current vibration");
+ Slog.d(TAG, "Ignoring incoming vibration in favor of current vibration");
}
return;
}
}
+ // If the current vibration is repeating and the incoming one is non-repeating, then ignore
+ // the non-repeating vibration. This is so that we don't cancel vibrations that are meant
+ // to grab the attention of the user, like ringtones and alarms, in favor of one-shot
+ // vibrations that are likely quite short.
+ if (!isRepeatingVibration(effect)
+ && mCurrentVibration != null && isRepeatingVibration(mCurrentVibration.mEffect)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
+ }
+ return;
+ }
+
Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
// Only link against waveforms since they potentially don't have a finish if
@@ -404,6 +416,16 @@ public class VibratorService extends IVibratorService.Stub
}
}
+ private static boolean isRepeatingVibration(VibrationEffect effect) {
+ if (effect instanceof VibrationEffect.Waveform) {
+ final VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+ if (waveform.getRepeatIndex() >= 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void addToPreviousVibrationsLocked(Vibration vib) {
if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
mPreviousVibrations.removeFirst();
diff --git a/com/android/server/accessibility/AccessibilityInputFilter.java b/com/android/server/accessibility/AccessibilityInputFilter.java
index c60647fa..f6fcaae4 100644
--- a/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -19,6 +19,9 @@ package com.android.server.accessibility;
import android.content.Context;
import android.os.Handler;
import android.os.PowerManager;
+import android.util.DebugUtils;
+import android.util.ExceptionUtils;
+import android.util.Log;
import android.util.Pools.SimplePool;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -31,6 +34,7 @@ import android.view.MotionEvent;
import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
+import com.android.internal.util.BitUtils;
import com.android.server.LocalServices;
/**
@@ -188,6 +192,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
if (mEventHandler == null) {
+ if (DEBUG) Slog.d(TAG, "mEventHandler == null for event " + event);
super.onInputEvent(event, policyFlags);
return;
}
@@ -339,6 +344,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
MotionEvent transformedEvent = MotionEvent.obtain(event);
mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
transformedEvent.recycle();
+ } else {
+ if (DEBUG) Slog.d(TAG, "mEventHandler == null for " + event);
}
}
@@ -366,11 +373,20 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
@Override
+ public EventStreamTransformation getNext() {
+ return null;
+ }
+
+ @Override
public void clearEvents(int inputSource) {
/* do nothing */
}
void setUserAndEnabledFeatures(int userId, int enabledFeatures) {
+ if (DEBUG) {
+ Slog.i(TAG, "setUserAndEnabledFeatures(userId = " + userId + ", enabledFeatures = 0x"
+ + Integer.toHexString(enabledFeatures) + ")");
+ }
if (mEnabledFeatures == enabledFeatures && mUserId == userId) {
return;
}
@@ -397,6 +413,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
private void enableFeatures() {
+ if (DEBUG) Slog.i(TAG, "enableFeatures()");
+
resetStreamState();
if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
@@ -443,7 +461,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
*/
private void addFirstEventHandler(EventStreamTransformation handler) {
if (mEventHandler != null) {
- handler.setNext(mEventHandler);
+ handler.setNext(mEventHandler);
} else {
handler.setNext(this);
}
diff --git a/com/android/server/accessibility/AutoclickController.java b/com/android/server/accessibility/AutoclickController.java
index 892e9da4..f5b0eb1e 100644
--- a/com/android/server/accessibility/AutoclickController.java
+++ b/com/android/server/accessibility/AutoclickController.java
@@ -23,15 +23,12 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.provider.Settings;
-import android.util.Slog;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
/**
@@ -55,11 +52,10 @@ import android.view.accessibility.AccessibilityManager;
*
* Each instance is associated to a single user (and it does not handle user switch itself).
*/
-public class AutoclickController implements EventStreamTransformation {
+public class AutoclickController extends BaseEventStreamTransformation {
private static final String LOG_TAG = AutoclickController.class.getSimpleName();
- private EventStreamTransformation mNext;
private final Context mContext;
private final int mUserId;
@@ -88,9 +84,7 @@ public class AutoclickController implements EventStreamTransformation {
mClickScheduler.cancel();
}
- if (mNext != null) {
- mNext.onMotionEvent(event, rawEvent, policyFlags);
- }
+ super.onMotionEvent(event, rawEvent, policyFlags);
}
@Override
@@ -103,21 +97,7 @@ public class AutoclickController implements EventStreamTransformation {
}
}
- if (mNext != null) {
- mNext.onKeyEvent(event, policyFlags);
- }
- }
-
- @Override
- public void onAccessibilityEvent(AccessibilityEvent event) {
- if (mNext != null) {
- mNext.onAccessibilityEvent(event);
- }
- }
-
- @Override
- public void setNext(EventStreamTransformation next) {
- mNext = next;
+ super.onKeyEvent(event, policyFlags);
}
@Override
@@ -126,9 +106,7 @@ public class AutoclickController implements EventStreamTransformation {
mClickScheduler.cancel();
}
- if (mNext != null) {
- mNext.clearEvents(inputSource);
- }
+ super.clearEvents(inputSource);
}
@Override
@@ -418,7 +396,7 @@ public class AutoclickController implements EventStreamTransformation {
* Creates and forwards click event sequence.
*/
private void sendClick() {
- if (mLastMotionEvent == null || mNext == null) {
+ if (mLastMotionEvent == null || getNext() == null) {
return;
}
@@ -448,10 +426,10 @@ public class AutoclickController implements EventStreamTransformation {
MotionEvent upEvent = MotionEvent.obtain(downEvent);
upEvent.setAction(MotionEvent.ACTION_UP);
- mNext.onMotionEvent(downEvent, downEvent, mEventPolicyFlags);
+ AutoclickController.super.onMotionEvent(downEvent, downEvent, mEventPolicyFlags);
downEvent.recycle();
- mNext.onMotionEvent(upEvent, upEvent, mEventPolicyFlags);
+ AutoclickController.super.onMotionEvent(upEvent, upEvent, mEventPolicyFlags);
upEvent.recycle();
}
diff --git a/com/android/server/accessibility/BaseEventStreamTransformation.java b/com/android/server/accessibility/BaseEventStreamTransformation.java
new file mode 100644
index 00000000..ce54586c
--- /dev/null
+++ b/com/android/server/accessibility/BaseEventStreamTransformation.java
@@ -0,0 +1,31 @@
+/*
+ ** Copyright 2017, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ ** http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+abstract class BaseEventStreamTransformation implements EventStreamTransformation {
+ private EventStreamTransformation mNext;
+
+ @Override
+ public void setNext(EventStreamTransformation next) {
+ mNext = next;
+ }
+
+ @Override
+ public EventStreamTransformation getNext() {
+ return mNext;
+ }
+} \ No newline at end of file
diff --git a/com/android/server/accessibility/EventStreamTransformation.java b/com/android/server/accessibility/EventStreamTransformation.java
index fdc40984..7982996e 100644
--- a/com/android/server/accessibility/EventStreamTransformation.java
+++ b/com/android/server/accessibility/EventStreamTransformation.java
@@ -65,7 +65,12 @@ interface EventStreamTransformation {
* @param rawEvent The raw motion event.
* @param policyFlags Policy flags for the event.
*/
- public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+ default void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ EventStreamTransformation next = getNext();
+ if (next != null) {
+ next.onMotionEvent(event, rawEvent, policyFlags);
+ }
+ }
/**
* Receives a key event.
@@ -73,14 +78,24 @@ interface EventStreamTransformation {
* @param event The key event.
* @param policyFlags Policy flags for the event.
*/
- public void onKeyEvent(KeyEvent event, int policyFlags);
+ default void onKeyEvent(KeyEvent event, int policyFlags) {
+ EventStreamTransformation next = getNext();
+ if (next != null) {
+ next.onKeyEvent(event, policyFlags);
+ }
+ }
/**
* Receives an accessibility event.
*
* @param event The accessibility event.
*/
- public void onAccessibilityEvent(AccessibilityEvent event);
+ default void onAccessibilityEvent(AccessibilityEvent event) {
+ EventStreamTransformation next = getNext();
+ if (next != null) {
+ next.onAccessibilityEvent(event);
+ }
+ };
/**
* Sets the next transformation.
@@ -90,14 +105,26 @@ interface EventStreamTransformation {
public void setNext(EventStreamTransformation next);
/**
+ * Gets the next transformation.
+ *
+ * @return The next transformation.
+ */
+ public EventStreamTransformation getNext();
+
+ /**
* Clears internal state associated with events from specific input source.
*
* @param inputSource The input source class for which transformation state should be cleared.
*/
- public void clearEvents(int inputSource);
+ default void clearEvents(int inputSource) {
+ EventStreamTransformation next = getNext();
+ if (next != null) {
+ next.clearEvents(inputSource);
+ }
+ }
/**
* Destroys this transformation.
*/
- public void onDestroy();
+ default void onDestroy() {}
}
diff --git a/com/android/server/accessibility/KeyboardInterceptor.java b/com/android/server/accessibility/KeyboardInterceptor.java
index f00a9540..77249452 100644
--- a/com/android/server/accessibility/KeyboardInterceptor.java
+++ b/com/android/server/accessibility/KeyboardInterceptor.java
@@ -22,14 +22,12 @@ import android.os.SystemClock;
import android.util.Pools;
import android.util.Slog;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.WindowManagerPolicy;
-import android.view.accessibility.AccessibilityEvent;
/**
* Intercepts key events and forwards them to accessibility manager service.
*/
-public class KeyboardInterceptor implements EventStreamTransformation, Handler.Callback {
+public class KeyboardInterceptor extends BaseEventStreamTransformation implements Handler.Callback {
private static final int MESSAGE_PROCESS_QUEUED_EVENTS = 1;
private static final String LOG_TAG = "KeyboardInterceptor";
@@ -37,7 +35,6 @@ public class KeyboardInterceptor implements EventStreamTransformation, Handler.C
private final WindowManagerPolicy mPolicy;
private final Handler mHandler;
- private EventStreamTransformation mNext;
private KeyEventHolder mEventQueueStart;
private KeyEventHolder mEventQueueEnd;
@@ -65,13 +62,6 @@ public class KeyboardInterceptor implements EventStreamTransformation, Handler.C
}
@Override
- public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (mNext != null) {
- mNext.onMotionEvent(event, rawEvent, policyFlags);
- }
- }
-
- @Override
public void onKeyEvent(KeyEvent event, int policyFlags) {
/*
* Certain keys have system-level behavior that affects accessibility services.
@@ -90,29 +80,6 @@ public class KeyboardInterceptor implements EventStreamTransformation, Handler.C
}
@Override
- public void onAccessibilityEvent(AccessibilityEvent event) {
- if (mNext != null) {
- mNext.onAccessibilityEvent(event);
- }
- }
-
- @Override
- public void setNext(EventStreamTransformation next) {
- mNext = next;
- }
-
- @Override
- public void clearEvents(int inputSource) {
- if (mNext != null) {
- mNext.clearEvents(inputSource);
- }
- }
-
- @Override
- public void onDestroy() {
- }
-
- @Override
public boolean handleMessage(Message msg) {
if (msg.what != MESSAGE_PROCESS_QUEUED_EVENTS) {
Slog.e(LOG_TAG, "Unexpected message type");
diff --git a/com/android/server/accessibility/MagnificationController.java b/com/android/server/accessibility/MagnificationController.java
index 98b8e6b7..a10b7a20 100644
--- a/com/android/server/accessibility/MagnificationController.java
+++ b/com/android/server/accessibility/MagnificationController.java
@@ -56,6 +56,7 @@ import java.util.Locale;
* constraints.
*/
class MagnificationController implements Handler.Callback {
+ private static final boolean DEBUG = false;
private static final String LOG_TAG = "MagnificationController";
public static final float MIN_SCALE = 1.0f;
@@ -509,6 +510,12 @@ class MagnificationController implements Handler.Callback {
private boolean setScaleAndCenterLocked(float scale, float centerX, float centerY,
boolean animate, int id) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG,
+ "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
+ + ", centerY = " + centerY + ", animate = " + animate + ", id = " + id
+ + ")");
+ }
final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
sendSpecToAnimation(mCurrentMagnificationSpec, animate);
if (isMagnifying() && (id != INVALID_ID)) {
@@ -535,7 +542,9 @@ class MagnificationController implements Handler.Callback {
final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
- updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
+ if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
+ onMagnificationChangedLocked();
+ }
if (id != INVALID_ID) {
mIdOfLastServiceToMagnify = id;
}
@@ -633,6 +642,11 @@ class MagnificationController implements Handler.Callback {
}
private boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG,
+ "updateCurrentSpecWithOffsetsLocked(nonNormOffsetX = " + nonNormOffsetX
+ + ", nonNormOffsetY = " + nonNormOffsetY + ")");
+ }
boolean changed = false;
final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
if (Float.compare(mCurrentMagnificationSpec.offsetX, offsetX) != 0) {
@@ -750,6 +764,9 @@ class MagnificationController implements Handler.Callback {
}
private void sendSpecToAnimation(MagnificationSpec spec, boolean animate) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "sendSpecToAnimation(spec = " + spec + ", animate = " + animate + ")");
+ }
if (Thread.currentThread().getId() == mMainThreadId) {
mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
} else {
diff --git a/com/android/server/accessibility/MagnificationGestureHandler.java b/com/android/server/accessibility/MagnificationGestureHandler.java
index d6452f87..9b2b4eb7 100644
--- a/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -42,14 +42,12 @@ import android.util.Slog;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.ViewConfiguration;
-import android.view.accessibility.AccessibilityEvent;
import com.android.internal.annotations.VisibleForTesting;
@@ -102,31 +100,23 @@ import com.android.internal.annotations.VisibleForTesting;
* 7. The magnification scale will be persisted in settings and in the cloud.
*/
@SuppressWarnings("WeakerAccess")
-class MagnificationGestureHandler implements EventStreamTransformation {
- private static final String LOG_TAG = "MagnificationEventHandler";
+class MagnificationGestureHandler extends BaseEventStreamTransformation {
+ private static final String LOG_TAG = "MagnificationGestureHandler";
private static final boolean DEBUG_ALL = false;
private static final boolean DEBUG_STATE_TRANSITIONS = false || DEBUG_ALL;
private static final boolean DEBUG_DETECTING = false || DEBUG_ALL;
- private static final boolean DEBUG_PANNING = false || DEBUG_ALL;
-
- /** @see #handleMotionEventStateDelegating */
- @VisibleForTesting static final int STATE_DELEGATING = 1;
- /** @see DetectingStateHandler */
- @VisibleForTesting static final int STATE_DETECTING = 2;
- /** @see ViewportDraggingStateHandler */
- @VisibleForTesting static final int STATE_VIEWPORT_DRAGGING = 3;
- /** @see PanningScalingStateHandler */
- @VisibleForTesting static final int STATE_PANNING_SCALING = 4;
+ private static final boolean DEBUG_PANNING_SCALING = false || DEBUG_ALL;
private static final float MIN_SCALE = 2.0f;
private static final float MAX_SCALE = 5.0f;
@VisibleForTesting final MagnificationController mMagnificationController;
- @VisibleForTesting final DetectingStateHandler mDetectingStateHandler;
- @VisibleForTesting final PanningScalingStateHandler mPanningScalingStateHandler;
- @VisibleForTesting final ViewportDraggingStateHandler mViewportDraggingStateHandler;
+ @VisibleForTesting final DelegatingState mDelegatingState;
+ @VisibleForTesting final DetectingState mDetectingState;
+ @VisibleForTesting final PanningScalingState mPanningScalingState;
+ @VisibleForTesting final ViewportDraggingState mViewportDraggingState;
private final ScreenStateReceiver mScreenStateReceiver;
@@ -138,21 +128,12 @@ class MagnificationGestureHandler implements EventStreamTransformation {
final boolean mDetectTripleTap;
/**
- * Whether {@link #mShortcutTriggered shortcut} is enabled
+ * Whether {@link DetectingState#mShortcutTriggered shortcut} is enabled
*/
final boolean mDetectShortcutTrigger;
- EventStreamTransformation mNext;
-
- @VisibleForTesting int mCurrentState;
- @VisibleForTesting int mPreviousState;
-
- @VisibleForTesting boolean mShortcutTriggered;
-
- /**
- * Time of last {@link MotionEvent#ACTION_DOWN} while in {@link #STATE_DELEGATING}
- */
- long mDelegatingStateDownTime;
+ @VisibleForTesting State mCurrentState;
+ @VisibleForTesting State mPreviousState;
private PointerCoords[] mTempPointerCoords;
private PointerProperties[] mTempPointerProperties;
@@ -174,10 +155,10 @@ class MagnificationGestureHandler implements EventStreamTransformation {
boolean detectShortcutTrigger) {
mMagnificationController = magnificationController;
- mDetectingStateHandler = new DetectingStateHandler(context);
- mViewportDraggingStateHandler = new ViewportDraggingStateHandler();
- mPanningScalingStateHandler =
- new PanningScalingStateHandler(context);
+ mDelegatingState = new DelegatingState();
+ mDetectingState = new DetectingState(context);
+ mViewportDraggingState = new ViewportDraggingState();
+ mPanningScalingState = new PanningScalingState(context);
mDetectTripleTap = detectTripleTap;
mDetectShortcutTrigger = detectShortcutTrigger;
@@ -189,62 +170,29 @@ class MagnificationGestureHandler implements EventStreamTransformation {
mScreenStateReceiver = null;
}
- transitionTo(STATE_DETECTING);
+ transitionTo(mDetectingState);
}
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (DEBUG_ALL) Slog.i(LOG_TAG, "onMotionEvent(" + event + ")");
+
if ((!mDetectTripleTap && !mDetectShortcutTrigger)
|| !event.isFromSource(SOURCE_TOUCHSCREEN)) {
dispatchTransformedEvent(event, rawEvent, policyFlags);
return;
}
- // Local copy to avoid dispatching the same event to more than one state handler
- // in case mPanningScalingStateHandler changes mCurrentState
- int currentState = mCurrentState;
- mPanningScalingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
- switch (currentState) {
- case STATE_DELEGATING: {
- handleMotionEventStateDelegating(event, rawEvent, policyFlags);
- }
- break;
- case STATE_DETECTING: {
- mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
- }
- break;
- case STATE_VIEWPORT_DRAGGING: {
- mViewportDraggingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
- }
- break;
- case STATE_PANNING_SCALING: {
- // mPanningScalingStateHandler handles events only
- // if this is the current state since it uses ScaleGestureDetector
- // and a GestureDetector which need well formed event stream.
- }
- break;
- default: {
- throw new IllegalStateException("Unknown state: " + currentState);
- }
- }
- }
- @Override
- public void onKeyEvent(KeyEvent event, int policyFlags) {
- if (mNext != null) {
- mNext.onKeyEvent(event, policyFlags);
- }
+ handleEventWith(mCurrentState, event, rawEvent, policyFlags);
}
- @Override
- public void onAccessibilityEvent(AccessibilityEvent event) {
- if (mNext != null) {
- mNext.onAccessibilityEvent(event);
- }
- }
+ private void handleEventWith(State stateHandler,
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ // To keep InputEventConsistencyVerifiers within GestureDetectors happy
+ mPanningScalingState.mScrollGestureDetector.onTouchEvent(event);
+ mPanningScalingState.mScaleGestureDetector.onTouchEvent(event);
- @Override
- public void setNext(EventStreamTransformation next) {
- mNext = next;
+ stateHandler.onMotionEvent(event, rawEvent, policyFlags);
}
@Override
@@ -253,13 +201,16 @@ class MagnificationGestureHandler implements EventStreamTransformation {
clearAndTransitionToStateDetecting();
}
- if (mNext != null) {
- mNext.clearEvents(inputSource);
- }
+ super.clearEvents(inputSource);
}
@Override
public void onDestroy() {
+ if (DEBUG_STATE_TRANSITIONS) {
+ Slog.i(LOG_TAG, "onDestroy(); delayed = "
+ + MotionEventInfo.toString(mDetectingState.mDelayedEventQueue));
+ }
+
if (mScreenStateReceiver != null) {
mScreenStateReceiver.unregister();
}
@@ -272,59 +223,21 @@ class MagnificationGestureHandler implements EventStreamTransformation {
if (wasMagnifying) {
clearAndTransitionToStateDetecting();
} else {
- toggleShortcutTriggered();
+ mDetectingState.toggleShortcutTriggered();
}
}
}
- private void toggleShortcutTriggered() {
- setShortcutTriggered(!mShortcutTriggered);
- }
-
- private void setShortcutTriggered(boolean state) {
- if (mShortcutTriggered == state) {
- return;
- }
-
- mShortcutTriggered = state;
- mMagnificationController.setForceShowMagnifiableBounds(state);
- }
-
void clearAndTransitionToStateDetecting() {
- setShortcutTriggered(false);
- mCurrentState = STATE_DETECTING;
- mDetectingStateHandler.clear();
- mViewportDraggingStateHandler.clear();
- mPanningScalingStateHandler.clear();
- }
-
- private void handleMotionEventStateDelegating(MotionEvent event,
- MotionEvent rawEvent, int policyFlags) {
- if (event.getActionMasked() == ACTION_UP) {
- transitionTo(STATE_DETECTING);
- }
- delegateEvent(event, rawEvent, policyFlags);
- }
-
- void delegateEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mDelegatingStateDownTime = event.getDownTime();
- }
- if (mNext != null) {
- // We cache some events to see if the user wants to trigger magnification.
- // If no magnification is triggered we inject these events with adjusted
- // time and down time to prevent subsequent transformations being confused
- // by stale events. After the cached events, which always have a down, are
- // injected we need to also update the down time of all subsequent non cached
- // events. All delegated events cached and non-cached are delivered here.
- event.setDownTime(mDelegatingStateDownTime);
- dispatchTransformedEvent(event, rawEvent, policyFlags);
- }
+ mCurrentState = mDelegatingState;
+ mDetectingState.clear();
+ mViewportDraggingState.clear();
+ mPanningScalingState.clear();
}
private void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
- if (mNext == null) return; // Nowhere to dispatch to
+ if (DEBUG_ALL) Slog.i(LOG_TAG, "dispatchTransformedEvent(event = " + event + ")");
// If the touchscreen event is within the magnified portion of the screen we have
// to change its location to be where the user thinks he is poking the
@@ -351,7 +264,7 @@ class MagnificationGestureHandler implements EventStreamTransformation {
coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(),
event.getFlags());
}
- mNext.onMotionEvent(event, rawEvent, policyFlags);
+ super.onMotionEvent(event, rawEvent, policyFlags);
}
private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
@@ -386,9 +299,10 @@ class MagnificationGestureHandler implements EventStreamTransformation {
return mTempPointerProperties;
}
- private void transitionTo(int state) {
+ private void transitionTo(State state) {
if (DEBUG_STATE_TRANSITIONS) {
- Slog.i(LOG_TAG, (stateToString(mCurrentState) + " -> " + stateToString(state)
+ Slog.i(LOG_TAG,
+ (State.nameOf(mCurrentState) + " -> " + State.nameOf(state)
+ " at " + asList(copyOfRange(new RuntimeException().getStackTrace(), 1, 5)))
.replace(getClass().getName(), ""));
}
@@ -396,40 +310,40 @@ class MagnificationGestureHandler implements EventStreamTransformation {
mCurrentState = state;
}
- private static String stateToString(int state) {
- switch (state) {
- case STATE_DELEGATING: return "STATE_DELEGATING";
- case STATE_DETECTING: return "STATE_DETECTING";
- case STATE_VIEWPORT_DRAGGING: return "STATE_VIEWPORT_DRAGGING";
- case STATE_PANNING_SCALING: return "STATE_PANNING_SCALING";
- case 0: return "0";
- default: throw new IllegalArgumentException("Unknown state: " + state);
- }
- }
+ interface State {
+ void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
- private interface MotionEventHandler {
+ default void clear() {}
- void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+ default String name() {
+ return getClass().getSimpleName();
+ }
- void clear();
+ static String nameOf(@Nullable State s) {
+ return s != null ? s.name() : "null";
+ }
}
/**
* This class determines if the user is performing a scale or pan gesture.
*
- * @see #STATE_PANNING_SCALING
+ * Unlike when {@link ViewportDraggingState dragging the viewport}, in panning mode the viewport
+ * moves in the same direction as the fingers, and allows to easily and precisely scale the
+ * magnification level.
+ * This makes it the preferred mode for one-off adjustments, due to its precision and ease of
+ * triggering.
*/
- final class PanningScalingStateHandler extends SimpleOnGestureListener
- implements OnScaleGestureListener, MotionEventHandler {
+ final class PanningScalingState extends SimpleOnGestureListener
+ implements OnScaleGestureListener, State {
private final ScaleGestureDetector mScaleGestureDetector;
- private final GestureDetector mGestureDetector;
+ private final GestureDetector mScrollGestureDetector;
final float mScalingThreshold;
float mInitialScaleFactor = -1;
boolean mScaling;
- public PanningScalingStateHandler(Context context) {
+ public PanningScalingState(Context context) {
final TypedValue scaleValue = new TypedValue();
context.getResources().getValue(
com.android.internal.R.dimen.config_screen_magnification_scaling_threshold,
@@ -437,35 +351,27 @@ class MagnificationGestureHandler implements EventStreamTransformation {
mScalingThreshold = scaleValue.getFloat();
mScaleGestureDetector = new ScaleGestureDetector(context, this);
mScaleGestureDetector.setQuickScaleEnabled(false);
- mGestureDetector = new GestureDetector(context, this);
+ mScrollGestureDetector = new GestureDetector(context, this);
}
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- // Dispatches #onScaleBegin, #onScale, #onScaleEnd
- mScaleGestureDetector.onTouchEvent(event);
- // Dispatches #onScroll
- mGestureDetector.onTouchEvent(event);
-
- if (mCurrentState != STATE_PANNING_SCALING) {
- return;
- }
-
int action = event.getActionMasked();
+
if (action == ACTION_POINTER_UP
&& event.getPointerCount() == 2 // includes the pointer currently being released
- && mPreviousState == STATE_VIEWPORT_DRAGGING) {
+ && mPreviousState == mViewportDraggingState) {
- persistScaleAndTransitionTo(STATE_VIEWPORT_DRAGGING);
+ persistScaleAndTransitionTo(mViewportDraggingState);
} else if (action == ACTION_UP) {
- persistScaleAndTransitionTo(STATE_DETECTING);
+ persistScaleAndTransitionTo(mDetectingState);
}
}
- public void persistScaleAndTransitionTo(int state) {
+ public void persistScaleAndTransitionTo(State state) {
mMagnificationController.persistScale();
clear();
transitionTo(state);
@@ -474,16 +380,16 @@ class MagnificationGestureHandler implements EventStreamTransformation {
@Override
public boolean onScroll(MotionEvent first, MotionEvent second,
float distanceX, float distanceY) {
- if (mCurrentState != STATE_PANNING_SCALING) {
+ if (mCurrentState != mPanningScalingState) {
return true;
}
- if (DEBUG_PANNING) {
+ if (DEBUG_PANNING_SCALING) {
Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
+ " scrollY: " + distanceY);
}
mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
- return true;
+ return /* event consumed: */ true;
}
@Override
@@ -494,12 +400,8 @@ class MagnificationGestureHandler implements EventStreamTransformation {
return false;
}
final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
- if (abs(deltaScale) > mScalingThreshold) {
- mScaling = true;
- return true;
- } else {
- return false;
- }
+ mScaling = abs(deltaScale) > mScalingThreshold;
+ return mScaling;
}
final float initialScale = mMagnificationController.getScale();
@@ -523,14 +425,15 @@ class MagnificationGestureHandler implements EventStreamTransformation {
final float pivotX = detector.getFocusX();
final float pivotY = detector.getFocusY();
+ if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x");
mMagnificationController.setScale(scale, pivotX, pivotY, false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
- return true;
+ return /* handled: */ true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
- return (mCurrentState == STATE_PANNING_SCALING);
+ return /* continue recognizing: */ (mCurrentState == mPanningScalingState);
}
@Override
@@ -546,7 +449,7 @@ class MagnificationGestureHandler implements EventStreamTransformation {
@Override
public String toString() {
- return "MagnifiedContentInteractionStateHandler{" +
+ return "PanningScalingState{" +
"mInitialScaleFactor=" + mInitialScaleFactor +
", mScaling=" + mScaling +
'}';
@@ -558,9 +461,11 @@ class MagnificationGestureHandler implements EventStreamTransformation {
* determined that the user is performing a single-finger drag of the
* magnification viewport.
*
- * @see #STATE_VIEWPORT_DRAGGING
+ * Unlike when {@link PanningScalingState panning}, the viewport moves in the opposite direction
+ * of the finger, and any part of the screen is reachable without lifting the finger.
+ * This makes it the preferable mode for tasks like reading text spanning full screen width.
*/
- final class ViewportDraggingStateHandler implements MotionEventHandler {
+ final class ViewportDraggingState implements State {
/** Whether to disable zoom after dragging ends */
boolean mZoomedInBeforeDrag;
@@ -572,7 +477,7 @@ class MagnificationGestureHandler implements EventStreamTransformation {
switch (action) {
case ACTION_POINTER_DOWN: {
clear();
- transitionTo(STATE_PANNING_SCALING);
+ transitionTo(mPanningScalingState);
}
break;
case ACTION_MOVE: {
@@ -594,7 +499,7 @@ class MagnificationGestureHandler implements EventStreamTransformation {
case ACTION_UP: {
if (!mZoomedInBeforeDrag) zoomOff();
clear();
- transitionTo(STATE_DETECTING);
+ transitionTo(mDetectingState);
}
break;
@@ -613,25 +518,51 @@ class MagnificationGestureHandler implements EventStreamTransformation {
@Override
public String toString() {
- return "ViewportDraggingStateHandler{" +
+ return "ViewportDraggingState{" +
"mZoomedInBeforeDrag=" + mZoomedInBeforeDrag +
", mLastMoveOutsideMagnifiedRegion=" + mLastMoveOutsideMagnifiedRegion +
'}';
}
}
+ final class DelegatingState implements State {
+ /**
+ * Time of last {@link MotionEvent#ACTION_DOWN} while in {@link DelegatingState}
+ */
+ public long mLastDelegatedDownEventTime;
+
+ @Override
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (event.getActionMasked() == ACTION_UP) {
+ transitionTo(mDetectingState);
+ }
+
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mLastDelegatedDownEventTime = event.getDownTime();
+ }
+ if (getNext() != null) {
+ // We cache some events to see if the user wants to trigger magnification.
+ // If no magnification is triggered we inject these events with adjusted
+ // time and down time to prevent subsequent transformations being confused
+ // by stale events. After the cached events, which always have a down, are
+ // injected we need to also update the down time of all subsequent non cached
+ // events. All delegated events cached and non-cached are delivered here.
+ event.setDownTime(mLastDelegatedDownEventTime);
+ dispatchTransformedEvent(event, rawEvent, policyFlags);
+ }
+ }
+ }
+
/**
* This class handles motion events when the event dispatch has not yet
* determined what the user is doing. It watches for various tap events.
- *
- * @see #STATE_DETECTING
*/
- final class DetectingStateHandler implements MotionEventHandler, Handler.Callback {
+ final class DetectingState implements State, Handler.Callback {
private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1;
private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
- final int mLongTapMinDelay = ViewConfiguration.getJumpTapTimeout();
+ final int mLongTapMinDelay;
final int mSwipeMinDistance;
final int mMultiTapMaxDelay;
final int mMultiTapMaxDistance;
@@ -642,9 +573,12 @@ class MagnificationGestureHandler implements EventStreamTransformation {
private MotionEvent mLastUp;
private MotionEvent mPreLastUp;
+ @VisibleForTesting boolean mShortcutTriggered;
+
Handler mHandler = new Handler(this);
- public DetectingStateHandler(Context context) {
+ public DetectingState(Context context) {
+ mLongTapMinDelay = ViewConfiguration.getLongPressTimeout();
mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout()
+ context.getResources().getInteger(
com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
@@ -661,7 +595,7 @@ class MagnificationGestureHandler implements EventStreamTransformation {
}
break;
case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
- transitionToDelegatingState(/* andClear */ true);
+ transitionToDelegatingStateAndClear();
}
break;
default: {
@@ -682,12 +616,12 @@ class MagnificationGestureHandler implements EventStreamTransformation {
if (!mMagnificationController.magnificationRegionContains(
event.getX(), event.getY())) {
- transitionToDelegatingState(/* andClear */ !mShortcutTriggered);
+ transitionToDelegatingStateAndClear();
} else if (isMultiTapTriggered(2 /* taps */)) {
// 3tap and hold
- delayedTransitionToDraggingState(event);
+ afterLongTapTimeoutTransitionToDraggingState(event);
} else if (mDetectTripleTap
// If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
@@ -695,21 +629,21 @@ class MagnificationGestureHandler implements EventStreamTransformation {
// STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
|| mMagnificationController.isMagnifying()) {
- delayedTransitionToDelegatingState();
+ afterMultiTapTimeoutTransitionToDelegatingState();
} else {
// Delegate pending events without delay
- transitionToDelegatingState(/* andClear */ true);
+ transitionToDelegatingStateAndClear();
}
}
break;
case ACTION_POINTER_DOWN: {
if (mMagnificationController.isMagnifying()) {
- transitionTo(STATE_PANNING_SCALING);
+ transitionTo(mPanningScalingState);
clear();
} else {
- transitionToDelegatingState(/* andClear */ true);
+ transitionToDelegatingStateAndClear();
}
}
break;
@@ -722,7 +656,7 @@ class MagnificationGestureHandler implements EventStreamTransformation {
&& !isMultiTapTriggered(2 /* taps */)) {
// Swipe detected - delegate skipping timeout
- transitionToDelegatingState(/* andClear */ true);
+ transitionToDelegatingStateAndClear();
}
}
break;
@@ -733,7 +667,7 @@ class MagnificationGestureHandler implements EventStreamTransformation {
if (!mMagnificationController.magnificationRegionContains(
event.getX(), event.getY())) {
- transitionToDelegatingState(/* andClear */ !mShortcutTriggered);
+ transitionToDelegatingStateAndClear();
} else if (isMultiTapTriggered(3 /* taps */)) {
@@ -742,12 +676,11 @@ class MagnificationGestureHandler implements EventStreamTransformation {
} else if (
// Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP
isFingerDown()
- //TODO long tap should never happen here
- && (timeBetween(mLastDown, /* mLastUp */ event) >= mLongTapMinDelay)
- || distance(mLastDown, /* mLastUp */ event)
- >= mSwipeMinDistance) {
+ //TODO long tap should never happen here
+ && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay)
+ || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))) {
- transitionToDelegatingState(/* andClear */ true);
+ transitionToDelegatingStateAndClear();
}
}
@@ -795,15 +728,15 @@ class MagnificationGestureHandler implements EventStreamTransformation {
return MotionEventInfo.countOf(mDelayedEventQueue, ACTION_UP);
}
- /** -> {@link #STATE_DELEGATING} */
- public void delayedTransitionToDelegatingState() {
+ /** -> {@link DelegatingState} */
+ public void afterMultiTapTimeoutTransitionToDelegatingState() {
mHandler.sendEmptyMessageDelayed(
MESSAGE_TRANSITION_TO_DELEGATING_STATE,
mMultiTapMaxDelay);
}
- /** -> {@link #STATE_VIEWPORT_DRAGGING} */
- public void delayedTransitionToDraggingState(MotionEvent event) {
+ /** -> {@link ViewportDraggingState} */
+ public void afterLongTapTimeoutTransitionToDraggingState(MotionEvent event) {
mHandler.sendMessageDelayed(
mHandler.obtainMessage(MESSAGE_ON_TRIPLE_TAP_AND_HOLD, event),
ViewConfiguration.getLongPressTimeout());
@@ -846,11 +779,7 @@ class MagnificationGestureHandler implements EventStreamTransformation {
MotionEventInfo info = mDelayedEventQueue;
mDelayedEventQueue = info.mNext;
- // Because MagnifiedInteractionStateHandler requires well-formed event stream
- mPanningScalingStateHandler.onMotionEvent(
- info.event, info.rawEvent, info.policyFlags);
-
- delegateEvent(info.event, info.rawEvent, info.policyFlags);
+ handleEventWith(mDelegatingState, info.event, info.rawEvent, info.policyFlags);
info.recycle();
}
@@ -868,10 +797,10 @@ class MagnificationGestureHandler implements EventStreamTransformation {
mLastUp = null;
}
- void transitionToDelegatingState(boolean andClear) {
- transitionTo(STATE_DELEGATING);
+ void transitionToDelegatingStateAndClear() {
+ transitionTo(mDelegatingState);
sendDelayedMotionEvents();
- if (andClear) clear();
+ clear();
}
private void onTripleTap(MotionEvent up) {
@@ -895,24 +824,40 @@ class MagnificationGestureHandler implements EventStreamTransformation {
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
clear();
- mViewportDraggingStateHandler.mZoomedInBeforeDrag =
+ mViewportDraggingState.mZoomedInBeforeDrag =
mMagnificationController.isMagnifying();
zoomOn(down.getX(), down.getY());
- transitionTo(STATE_VIEWPORT_DRAGGING);
+ transitionTo(mViewportDraggingState);
}
@Override
public String toString() {
- return "DetectingStateHandler{" +
+ return "DetectingState{" +
"tapCount()=" + tapCount() +
+ ", mShortcutTriggered=" + mShortcutTriggered +
", mDelayedEventQueue=" + MotionEventInfo.toString(mDelayedEventQueue) +
'}';
}
+
+ void toggleShortcutTriggered() {
+ setShortcutTriggered(!mShortcutTriggered);
+ }
+
+ void setShortcutTriggered(boolean state) {
+ if (mShortcutTriggered == state) {
+ return;
+ }
+
+ mShortcutTriggered = state;
+ mMagnificationController.setForceShowMagnifiableBounds(state);
+ }
}
private void zoomOn(float centerX, float centerY) {
+ if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOn(" + centerX + ", " + centerY + ")");
+
final float scale = MathUtils.constrain(
mMagnificationController.getPersistedScale(),
MIN_SCALE, MAX_SCALE);
@@ -923,6 +868,8 @@ class MagnificationGestureHandler implements EventStreamTransformation {
}
private void zoomOff() {
+ if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
+
mMagnificationController.reset(/* animate */ true);
}
@@ -935,16 +882,15 @@ class MagnificationGestureHandler implements EventStreamTransformation {
@Override
public String toString() {
- return "MagnificationGestureHandler{" +
- "mDetectingStateHandler=" + mDetectingStateHandler +
- ", mMagnifiedInteractionStateHandler=" + mPanningScalingStateHandler +
- ", mViewportDraggingStateHandler=" + mViewportDraggingStateHandler +
+ return "MagnificationGesture{" +
+ "mDetectingState=" + mDetectingState +
+ ", mDelegatingState=" + mDelegatingState +
+ ", mMagnifiedInteractionState=" + mPanningScalingState +
+ ", mViewportDraggingState=" + mViewportDraggingState +
", mDetectTripleTap=" + mDetectTripleTap +
", mDetectShortcutTrigger=" + mDetectShortcutTrigger +
- ", mCurrentState=" + stateToString(mCurrentState) +
- ", mPreviousState=" + stateToString(mPreviousState) +
- ", mShortcutTriggered=" + mShortcutTriggered +
- ", mDelegatingStateDownTime=" + mDelegatingStateDownTime +
+ ", mCurrentState=" + State.nameOf(mCurrentState) +
+ ", mPreviousState=" + State.nameOf(mPreviousState) +
", mMagnificationController=" + mMagnificationController +
'}';
}
@@ -1051,7 +997,7 @@ class MagnificationGestureHandler implements EventStreamTransformation {
@Override
public void onReceive(Context context, Intent intent) {
- mGestureHandler.setShortcutTriggered(false);
+ mGestureHandler.mDetectingState.setShortcutTriggered(false);
}
}
}
diff --git a/com/android/server/accessibility/MotionEventInjector.java b/com/android/server/accessibility/MotionEventInjector.java
index 48041adb..b6b78129 100644
--- a/com/android/server/accessibility/MotionEventInjector.java
+++ b/com/android/server/accessibility/MotionEventInjector.java
@@ -30,13 +30,13 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.InputDevice;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManagerPolicy;
-import android.view.accessibility.AccessibilityEvent;
+
import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -45,7 +45,7 @@ import java.util.List;
* <p>
* All methods except {@code injectEvents} must be called only from the main thread.
*/
-public class MotionEventInjector implements EventStreamTransformation, Handler.Callback {
+public class MotionEventInjector extends BaseEventStreamTransformation implements Handler.Callback {
private static final String LOG_TAG = "MotionEventInjector";
private static final int MESSAGE_SEND_MOTION_EVENT = 1;
private static final int MESSAGE_INJECT_EVENTS = 2;
@@ -68,7 +68,6 @@ public class MotionEventInjector implements EventStreamTransformation, Handler.C
private final Handler mHandler;
private final SparseArray<Boolean> mOpenGesturesInProgress = new SparseArray<>();
- private EventStreamTransformation mNext;
private IAccessibilityServiceClient mServiceInterfaceForCurrentGesture;
private IntArray mSequencesInProgress = new IntArray(5);
private boolean mIsDestroyed = false;
@@ -117,25 +116,6 @@ public class MotionEventInjector implements EventStreamTransformation, Handler.C
}
@Override
- public void onKeyEvent(KeyEvent event, int policyFlags) {
- if (mNext != null) {
- mNext.onKeyEvent(event, policyFlags);
- }
- }
-
- @Override
- public void onAccessibilityEvent(AccessibilityEvent event) {
- if (mNext != null) {
- mNext.onAccessibilityEvent(event);
- }
- }
-
- @Override
- public void setNext(EventStreamTransformation next) {
- mNext = next;
- }
-
- @Override
public void clearEvents(int inputSource) {
/*
* Reset state for motion events passing through so we won't send a cancel event for
@@ -187,7 +167,7 @@ public class MotionEventInjector implements EventStreamTransformation, Handler.C
return;
}
- if (mNext == null) {
+ if (getNext() == null) {
notifyService(serviceInterface, sequence, false);
return;
}
@@ -262,17 +242,24 @@ public class MotionEventInjector implements EventStreamTransformation, Handler.C
int continuedPointerId = mStrokeIdToPointerId
.get(touchPoint.mContinuedStrokeId, -1);
if (continuedPointerId == -1) {
+ Slog.w(LOG_TAG, "Can't continue gesture due to unknown continued stroke id in "
+ + touchPoint);
return false;
}
mStrokeIdToPointerId.put(touchPoint.mStrokeId, continuedPointerId);
int lastPointIndex = findPointByStrokeId(
mLastTouchPoints, mNumLastTouchPoints, touchPoint.mContinuedStrokeId);
if (lastPointIndex < 0) {
+ Slog.w(LOG_TAG, "Can't continue gesture due continued gesture id of "
+ + touchPoint + " not matching any previous strokes in "
+ + Arrays.asList(mLastTouchPoints));
return false;
}
if (mLastTouchPoints[lastPointIndex].mIsEndOfPath
|| (mLastTouchPoints[lastPointIndex].mX != touchPoint.mX)
|| (mLastTouchPoints[lastPointIndex].mY != touchPoint.mY)) {
+ Slog.w(LOG_TAG, "Can't continue gesture due to points mismatch between "
+ + mLastTouchPoints[lastPointIndex] + " and " + touchPoint);
return false;
}
// Update the last touch point to match the continuation, so the gestures will
@@ -292,8 +279,8 @@ public class MotionEventInjector implements EventStreamTransformation, Handler.C
private void sendMotionEventToNext(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
- if (mNext != null) {
- mNext.onMotionEvent(event, rawEvent, policyFlags);
+ if (getNext() != null) {
+ super.onMotionEvent(event, rawEvent, policyFlags);
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mOpenGesturesInProgress.put(event.getSource(), true);
}
@@ -305,7 +292,7 @@ public class MotionEventInjector implements EventStreamTransformation, Handler.C
}
private void cancelAnyGestureInProgress(int source) {
- if ((mNext != null) && mOpenGesturesInProgress.get(source, false)) {
+ if ((getNext() != null) && mOpenGesturesInProgress.get(source, false)) {
long now = SystemClock.uptimeMillis();
MotionEvent cancelEvent =
obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1);
diff --git a/com/android/server/accessibility/TouchExplorer.java b/com/android/server/accessibility/TouchExplorer.java
index e380f2c6..a32686df 100644
--- a/com/android/server/accessibility/TouchExplorer.java
+++ b/com/android/server/accessibility/TouchExplorer.java
@@ -55,7 +55,8 @@ import java.util.List;
*
* @hide
*/
-class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDetector.Listener {
+class TouchExplorer extends BaseEventStreamTransformation
+ implements AccessibilityGestureDetector.Listener {
private static final boolean DEBUG = false;
@@ -131,9 +132,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
// the two dragging pointers as opposed to use the location of the primary one.
private final int mScaledMinPointerDistanceToUseMiddleLocation;
- // The handler to which to delegate events.
- private EventStreamTransformation mNext;
-
// Helper class to track received pointers.
private final ReceivedPointerTracker mReceivedPointerTracker;
@@ -198,9 +196,7 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
if (inputSource == InputDevice.SOURCE_TOUCHSCREEN) {
clear();
}
- if (mNext != null) {
- mNext.clearEvents(inputSource);
- }
+ super.clearEvents(inputSource);
}
@Override
@@ -258,16 +254,9 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
}
@Override
- public void setNext(EventStreamTransformation next) {
- mNext = next;
- }
-
- @Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
- if (mNext != null) {
- mNext.onMotionEvent(event, rawEvent, policyFlags);
- }
+ super.onMotionEvent(event, rawEvent, policyFlags);
return;
}
@@ -311,13 +300,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
}
@Override
- public void onKeyEvent(KeyEvent event, int policyFlags) {
- if (mNext != null) {
- mNext.onKeyEvent(event, policyFlags);
- }
- }
-
- @Override
public void onAccessibilityEvent(AccessibilityEvent event) {
final int eventType = event.getEventType();
@@ -353,9 +335,7 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
mLastTouchedWindowId = event.getWindowId();
} break;
}
- if (mNext != null) {
- mNext.onAccessibilityEvent(event);
- }
+ super.onAccessibilityEvent(event);
}
@Override
@@ -969,12 +949,10 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
// Make sure that the user will see the event.
policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
- if (mNext != null) {
- // TODO: For now pass null for the raw event since the touch
- // explorer is the last event transformation and it does
- // not care about the raw event.
- mNext.onMotionEvent(event, null, policyFlags);
- }
+ // TODO: For now pass null for the raw event since the touch
+ // explorer is the last event transformation and it does
+ // not care about the raw event.
+ super.onMotionEvent(event, null, policyFlags);
mInjectedPointerTracker.onMotionEvent(event);
diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java
index 8bcbfbef..6ed05552 100644
--- a/com/android/server/am/ActivityDisplay.java
+++ b/com/android/server/am/ActivityDisplay.java
@@ -16,9 +16,7 @@
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_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;
@@ -48,13 +46,14 @@ import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.ConfigurationContainer;
+import java.io.PrintWriter;
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 {
+class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_AM;
private static final String TAG_STACK = TAG + POSTFIX_STACK;
@@ -68,7 +67,7 @@ class ActivityDisplay extends ConfigurationContainer {
/** 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<>();
+ private final ArrayList<ActivityStack> mStacks = new ArrayList<>();
/** Array of all UIDs that are present on the display. */
private IntArray mDisplayAccessUIDs = new IntArray();
@@ -80,6 +79,13 @@ class ActivityDisplay extends ConfigurationContainer {
private boolean mSleeping;
+ // Cached reference to some special stacks we tend to get a lot so we don't need to loop
+ // through the list to find them.
+ private ActivityStack mHomeStack = null;
+ private ActivityStack mRecentsStack = null;
+ private ActivityStack mPinnedStack = null;
+ private ActivityStack mSplitScreenPrimaryStack = null;
+
ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
mSupervisor = supervisor;
mDisplayId = displayId;
@@ -98,6 +104,7 @@ class ActivityDisplay extends ConfigurationContainer {
}
if (DEBUG_STACK) Slog.v(TAG_STACK, "addChild: attaching " + stack
+ " to displayId=" + mDisplayId + " position=" + position);
+ addStackReferenceIfNeeded(stack);
positionChildAt(stack, position);
mSupervisor.mService.updateSleepIfNeededLocked();
}
@@ -106,6 +113,7 @@ class ActivityDisplay extends ConfigurationContainer {
if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack
+ " from displayId=" + mDisplayId);
mStacks.remove(stack);
+ removeStackReferenceIfNeeded(stack);
mSupervisor.mService.updateSleepIfNeededLocked();
}
@@ -150,6 +158,16 @@ class ActivityDisplay extends ConfigurationContainer {
* @see ConfigurationContainer#isCompatible(int, int)
*/
<T extends ActivityStack> T getStack(int windowingMode, int activityType) {
+ if (activityType == ACTIVITY_TYPE_HOME) {
+ return (T) mHomeStack;
+ } else if (activityType == ACTIVITY_TYPE_RECENTS) {
+ return (T) mRecentsStack;
+ }
+ if (windowingMode == WINDOWING_MODE_PINNED) {
+ return (T) mPinnedStack;
+ } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ return (T) mSplitScreenPrimaryStack;
+ }
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?
@@ -213,10 +231,14 @@ class ActivityDisplay extends ConfigurationContainer {
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;
+ windowingMode = getWindowingMode(); // Put in current display's windowing mode
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ // Else fullscreen for now...
+ windowingMode = WINDOWING_MODE_FULLSCREEN;
+ }
}
- final boolean inSplitScreenMode = hasSplitScreenStack();
+ final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
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
@@ -228,24 +250,7 @@ class ActivityDisplay extends ConfigurationContainer {
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 int stackId = mSupervisor.getNextStackId();
final T stack = createStackUnchecked(windowingMode, activityType, stackId, onTop);
@@ -291,7 +296,7 @@ class ActivityDisplay extends ConfigurationContainer {
if (stack.getWindowingMode() != windowingMode) {
continue;
}
- mSupervisor.removeStackLocked(stack.mStackId);
+ mSupervisor.removeStack(stack);
}
}
}
@@ -306,12 +311,63 @@ class ActivityDisplay extends ConfigurationContainer {
for (int i = mStacks.size() - 1; i >= 0; --i) {
final ActivityStack stack = mStacks.get(i);
if (stack.getActivityType() == activityType) {
- mSupervisor.removeStackLocked(stack.mStackId);
+ mSupervisor.removeStack(stack);
}
}
}
}
+ void onStackWindowingModeChanged(ActivityStack stack) {
+ removeStackReferenceIfNeeded(stack);
+ addStackReferenceIfNeeded(stack);
+ }
+
+ private void addStackReferenceIfNeeded(ActivityStack stack) {
+ final int activityType = stack.getActivityType();
+ final int windowingMode = stack.getWindowingMode();
+
+ if (activityType == ACTIVITY_TYPE_HOME) {
+ if (mHomeStack != null && mHomeStack != stack) {
+ throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
+ + mHomeStack + " already exist on display=" + this + " stack=" + stack);
+ }
+ mHomeStack = stack;
+ } else if (activityType == ACTIVITY_TYPE_RECENTS) {
+ if (mRecentsStack != null && mRecentsStack != stack) {
+ throw new IllegalArgumentException("addStackReferenceIfNeeded: recents stack="
+ + mRecentsStack + " already exist on display=" + this + " stack=" + stack);
+ }
+ mRecentsStack = stack;
+ }
+ if (windowingMode == WINDOWING_MODE_PINNED) {
+ if (mPinnedStack != null && mPinnedStack != stack) {
+ throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack="
+ + mPinnedStack + " already exist on display=" + this
+ + " stack=" + stack);
+ }
+ mPinnedStack = stack;
+ } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ if (mSplitScreenPrimaryStack != null && mSplitScreenPrimaryStack != stack) {
+ throw new IllegalArgumentException("addStackReferenceIfNeeded:"
+ + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack
+ + " already exist on display=" + this + " stack=" + stack);
+ }
+ mSplitScreenPrimaryStack = stack;
+ }
+ }
+
+ private void removeStackReferenceIfNeeded(ActivityStack stack) {
+ if (stack == mHomeStack) {
+ mHomeStack = null;
+ } else if (stack == mRecentsStack) {
+ mRecentsStack = null;
+ } else if (stack == mPinnedStack) {
+ mPinnedStack = null;
+ } else if (stack == mSplitScreenPrimaryStack) {
+ mSplitScreenPrimaryStack = null;
+ }
+ }
+
/** 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) {
@@ -326,20 +382,42 @@ class ActivityDisplay extends ConfigurationContainer {
return ACTIVITY_TYPE_UNDEFINED;
}
- ActivityStack getSplitScreenStack() {
- return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+ /**
+ * Get the topmost stack on the display. It may be different from focused stack, because
+ * focus may be on another display.
+ */
+ ActivityStack getTopStack() {
+ return mStacks.isEmpty() ? null : mStacks.get(mStacks.size() - 1);
}
- boolean hasSplitScreenStack() {
- return getSplitScreenStack() != null;
+ boolean isTopStack(ActivityStack stack) {
+ return stack == getTopStack();
+ }
+
+ int getIndexOf(ActivityStack stack) {
+ return mStacks.indexOf(stack);
+ }
+
+ void onLockTaskPackagesUpdated() {
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ mStacks.get(i).onLockTaskPackagesUpdated();
+ }
+ }
+
+ ActivityStack getSplitScreenPrimaryStack() {
+ return mSplitScreenPrimaryStack;
+ }
+
+ boolean hasSplitScreenPrimaryStack() {
+ return mSplitScreenPrimaryStack != null;
}
PinnedActivityStack getPinnedStack() {
- return getStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+ return (PinnedActivityStack) mPinnedStack;
}
boolean hasPinnedStack() {
- return getPinnedStack() != null;
+ return mPinnedStack != null;
}
@Override
@@ -353,7 +431,7 @@ class ActivityDisplay extends ConfigurationContainer {
}
@Override
- protected ConfigurationContainer getChildAt(int index) {
+ protected ActivityStack getChildAt(int index) {
return mStacks.get(index);
}
@@ -401,6 +479,10 @@ class ActivityDisplay extends ConfigurationContainer {
mSleeping = asleep;
}
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "displayId=" + mDisplayId + " mStacks=" + mStacks);
+ }
+
public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
super.writeToProto(proto, CONFIGURATION_CONTAINER);
diff --git a/com/android/server/am/ActivityManagerDebugConfig.java b/com/android/server/am/ActivityManagerDebugConfig.java
index 3a9bf125..ceb2ad62 100644
--- a/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/com/android/server/am/ActivityManagerDebugConfig.java
@@ -59,7 +59,6 @@ class ActivityManagerDebugConfig {
static final boolean DEBUG_FOCUS = false;
static final boolean DEBUG_IDLE = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_IMMERSIVE = DEBUG_ALL || false;
- static final boolean DEBUG_LOCKSCREEN = DEBUG_ALL || false;
static final boolean DEBUG_LOCKTASK = DEBUG_ALL || false;
static final boolean DEBUG_LRU = DEBUG_ALL || false;
static final boolean DEBUG_MU = DEBUG_ALL || false;
@@ -74,10 +73,10 @@ class ActivityManagerDebugConfig {
static final boolean DEBUG_PROVIDER = DEBUG_ALL || false;
static final boolean DEBUG_PSS = DEBUG_ALL || false;
static final boolean DEBUG_RECENTS = DEBUG_ALL || false;
+ static final boolean DEBUG_RECENTS_TRIM_TASKS = DEBUG_RECENTS || false;
static final boolean DEBUG_RELEASE = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_RESULTS = DEBUG_ALL || false;
static final boolean DEBUG_SAVED_STATE = DEBUG_ALL_ACTIVITIES || false;
- static final boolean DEBUG_SCREENSHOTS = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
static final boolean DEBUG_FOREGROUND_SERVICE = DEBUG_ALL || false;
static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false;
@@ -85,7 +84,6 @@ class ActivityManagerDebugConfig {
static final boolean DEBUG_STATES = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_SWITCH = DEBUG_ALL || false;
static final boolean DEBUG_TASKS = DEBUG_ALL || false;
- static final boolean DEBUG_THUMBNAILS = DEBUG_ALL || false;
static final boolean DEBUG_TRANSITION = DEBUG_ALL || false;
static final boolean DEBUG_UID_OBSERVERS = DEBUG_ALL || false;
static final boolean DEBUG_URI_PERMISSION = DEBUG_ALL || false;
@@ -105,7 +103,6 @@ class ActivityManagerDebugConfig {
static final String POSTFIX_FOCUS = (APPEND_CATEGORY_NAME) ? "_Focus" : "";
static final String POSTFIX_IDLE = (APPEND_CATEGORY_NAME) ? "_Idle" : "";
static final String POSTFIX_IMMERSIVE = (APPEND_CATEGORY_NAME) ? "_Immersive" : "";
- static final String POSTFIX_LOCKSCREEN = (APPEND_CATEGORY_NAME) ? "_LockScreen" : "";
static final String POSTFIX_LOCKTASK = (APPEND_CATEGORY_NAME) ? "_LockTask" : "";
static final String POSTFIX_LRU = (APPEND_CATEGORY_NAME) ? "_LRU" : "";
static final String POSTFIX_MU = "_MU";
@@ -122,7 +119,6 @@ class ActivityManagerDebugConfig {
static final String POSTFIX_RELEASE = (APPEND_CATEGORY_NAME) ? "_Release" : "";
static final String POSTFIX_RESULTS = (APPEND_CATEGORY_NAME) ? "_Results" : "";
static final String POSTFIX_SAVED_STATE = (APPEND_CATEGORY_NAME) ? "_SavedState" : "";
- static final String POSTFIX_SCREENSHOTS = (APPEND_CATEGORY_NAME) ? "_Screenshots" : "";
static final String POSTFIX_SERVICE = (APPEND_CATEGORY_NAME) ? "_Service" : "";
static final String POSTFIX_SERVICE_EXECUTING =
(APPEND_CATEGORY_NAME) ? "_ServiceExecuting" : "";
@@ -130,13 +126,11 @@ class ActivityManagerDebugConfig {
static final String POSTFIX_STATES = (APPEND_CATEGORY_NAME) ? "_States" : "";
static final String POSTFIX_SWITCH = (APPEND_CATEGORY_NAME) ? "_Switch" : "";
static final String POSTFIX_TASKS = (APPEND_CATEGORY_NAME) ? "_Tasks" : "";
- static final String POSTFIX_THUMBNAILS = (APPEND_CATEGORY_NAME) ? "_Thumbnails" : "";
static final String POSTFIX_TRANSITION = (APPEND_CATEGORY_NAME) ? "_Transition" : "";
static final String POSTFIX_UID_OBSERVERS = (APPEND_CATEGORY_NAME)
? "_UidObservers" : "";
static final String POSTFIX_URI_PERMISSION = (APPEND_CATEGORY_NAME) ? "_UriPermission" : "";
static final String POSTFIX_USER_LEAVING = (APPEND_CATEGORY_NAME) ? "_UserLeaving" : "";
static final String POSTFIX_VISIBILITY = (APPEND_CATEGORY_NAME) ? "_Visibility" : "";
- static final String POSTFIX_VISIBLE_BEHIND = (APPEND_CATEGORY_NAME) ? "_VisibleBehind" : "";
}
diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java
index e6fe6204..f17c9ac3 100644
--- a/com/android/server/am/ActivityManagerService.java
+++ b/com/android/server/am/ActivityManagerService.java
@@ -27,18 +27,10 @@ import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_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.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;
@@ -57,6 +49,9 @@ import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
import static android.os.Build.VERSION_CODES.N;
+import static android.os.IServiceManager.DUMP_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_PRIORITY_HIGH;
+import static android.os.IServiceManager.DUMP_PRIORITY_NORMAL;
import static android.os.Process.BLUETOOTH_UID;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.FIRST_ISOLATED_UID;
@@ -137,7 +132,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
@@ -182,7 +176,6 @@ 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_NONE;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE;
@@ -199,7 +192,6 @@ import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityManager.StackId;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityManagerInternal;
@@ -216,7 +208,6 @@ import android.app.ContentProviderHolder;
import android.app.Dialog;
import android.app.IActivityController;
import android.app.IActivityManager;
-import android.app.IAppTask;
import android.app.IApplicationThread;
import android.app.IInstrumentationWatcher;
import android.app.INotificationManager;
@@ -404,6 +395,9 @@ import com.android.server.SystemServiceManager;
import com.android.server.ThreadPriorityBooster;
import com.android.server.Watchdog;
import com.android.server.am.ActivityStack.ActivityState;
+import com.android.server.am.proto.ActivityManagerServiceProto;
+import com.android.server.am.proto.BroadcastProto;
+import com.android.server.am.proto.StickyBroadcastProto;
import com.android.server.firewall.IntentFirewall;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.pm.Installer;
@@ -739,9 +733,6 @@ public class ActivityManagerService extends IActivityManager.Stub
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
@@ -753,6 +744,8 @@ public class ActivityManagerService extends IActivityManager.Stub
public boolean canShowErrorDialogs() {
return mShowDialogs && !mSleeping && !mShuttingDown
&& !mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)
+ && !mUserController.hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
+ mUserController.getCurrentUserId())
&& !(UserManager.isDeviceInDemoMode(mContext)
&& mUserController.getCurrentUser().isDemo());
}
@@ -1717,7 +1710,6 @@ public class ActivityManagerService extends IActivityManager.Stub
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 TOP_APP_KILLED_BY_LMK_MSG = 73;
static final int NOTIFY_VR_KEYGUARD_MSG = 74;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
@@ -1738,9 +1730,6 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
private boolean mUserIsMonkey;
- /** Flag whether the device has a Recents UI */
- boolean mHasRecents;
-
/** The dimensions of the thumbnails in the Recents UI. */
int mThumbnailWidth;
int mThumbnailHeight;
@@ -1946,17 +1935,6 @@ 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;
@@ -2111,7 +2089,8 @@ public class ActivityManagerService extends IActivityManager.Stub
String text = mContext.getString(R.string.heavy_weight_notification,
context.getApplicationInfo().loadLabel(context.getPackageManager()));
Notification notification =
- new Notification.Builder(context, SystemNotificationChannels.DEVELOPER)
+ new Notification.Builder(context,
+ SystemNotificationChannels.HEAVY_WEIGHT_APP)
.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
.setWhen(0)
.setOngoing(true)
@@ -2145,7 +2124,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
try {
inm.cancelNotificationWithTag("android", null,
- SystemMessage.NOTE_HEAVY_WEIGHT_NOTIFICATION, msg.arg1);
+ SystemMessage.NOTE_HEAVY_WEIGHT_NOTIFICATION, msg.arg1);
} catch (RuntimeException e) {
Slog.w(ActivityManagerService.TAG,
"Error canceling notification for service", e);
@@ -2503,13 +2482,16 @@ public class ActivityManagerService extends IActivityManager.Stub
public void setSystemProcess() {
try {
- ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
+ ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true,
+ DUMP_PRIORITY_CRITICAL | DUMP_PRIORITY_NORMAL);
ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
- ServiceManager.addService("meminfo", new MemBinder(this));
+ ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false,
+ DUMP_PRIORITY_HIGH | DUMP_PRIORITY_NORMAL);
ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
ServiceManager.addService("dbinfo", new DbBinder(this));
if (MONITOR_CPU_USAGE) {
- ServiceManager.addService("cpuinfo", new CpuBinder(this));
+ ServiceManager.addService("cpuinfo", new CpuBinder(this),
+ /* allowIsolated= */ false, DUMP_PRIORITY_CRITICAL);
}
ServiceManager.addService("permission", new PermissionController(this));
ServiceManager.addService("processinfo", new ProcessInfoService(this));
@@ -2540,7 +2522,6 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized (this) {
mWindowManager = wm;
mStackSupervisor.setWindowManager(wm);
- mActivityStarter.setWindowManager(wm);
mLockTaskController.setWindowManager(wm);
}
}
@@ -2782,8 +2763,9 @@ public class ActivityManagerService extends IActivityManager.Stub
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
mTaskChangeNotificationController =
new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
- mActivityStarter = new ActivityStarter(this, mStackSupervisor);
+ mActivityStarter = new ActivityStarter(this);
mRecentTasks = new RecentTasks(this, mStackSupervisor);
+ mStackSupervisor.setRecentTasks(mRecentTasks);
mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
mProcessCpuThread = new Thread("CpuTracker") {
@@ -3241,11 +3223,12 @@ public class ActivityManagerService extends IActivityManager.Stub
// stack implementation changes in the future, keep in mind that the use of the fullscreen
// stack is a means to move the activity to the main display and a moveActivityToDisplay()
// option would be a better choice here.
- if (r.requestedVrComponent != null && r.getStackId() >= FIRST_DYNAMIC_STACK_ID) {
+ if (r.requestedVrComponent != null && r.getDisplayId() != DEFAULT_DISPLAY) {
Slog.i(TAG, "Moving " + r.shortComponentName + " from stack " + r.getStackId()
+ " to main stack for VR");
- setTaskWindowingMode(r.getTask().taskId,
- WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, true /* toTop */);
+ final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getOrCreateStack(
+ WINDOWING_MODE_FULLSCREEN, r.getActivityType(), true /* toTop */);
+ moveTaskToStack(r.getTask().taskId, stack.mStackId, true /* toTop */);
}
mHandler.sendMessage(
mHandler.obtainMessage(VR_MODE_CHANGE_MSG, 0, 0, r));
@@ -5095,11 +5078,12 @@ public class ActivityManagerService extends IActivityManager.Stub
}
synchronized(this) {
- if (mHeavyWeightProcess == null) {
+ final ProcessRecord proc = mHeavyWeightProcess;
+ if (proc == null) {
return;
}
- ArrayList<ActivityRecord> activities = new ArrayList<>(mHeavyWeightProcess.activities);
+ ArrayList<ActivityRecord> activities = new ArrayList<>(proc.activities);
for (int i = 0; i < activities.size(); i++) {
ActivityRecord r = activities.get(i);
if (!r.finishing && r.isInStackLocked()) {
@@ -5109,7 +5093,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
- mHeavyWeightProcess.userId, 0));
+ proc.userId, 0));
mHeavyWeightProcess = null;
}
}
@@ -5428,7 +5412,6 @@ 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));
@@ -5462,23 +5445,6 @@ 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
@@ -5963,16 +5929,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (appInfo != null) {
forceStopPackageLocked(packageName, appInfo.uid, "clear data");
- // Remove all tasks match the cleared application package and user
- for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
- final TaskRecord tr = mRecentTasks.get(i);
- final String taskPackageName =
- tr.getBaseIntent().getComponent().getPackageName();
- if (tr.userId != resolvedUserId) continue;
- if (!taskPackageName.equals(packageName)) continue;
- mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
- REMOVE_FROM_RECENTS);
- }
+ mRecentTasks.removeTasksByPackageName(packageName, resolvedUserId);
}
}
@@ -6553,7 +6510,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
// Clean-up disabled tasks
- cleanupDisabledPackageTasksLocked(packageName, disabledClasses, userId);
+ mRecentTasks.cleanupDisabledPackageTasksLocked(packageName, disabledClasses, userId);
// Clean-up disabled services.
mServices.bringDownDisabledPackageServicesLocked(
@@ -8084,8 +8041,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
private boolean isInPictureInPictureMode(ActivityRecord r) {
- if (r == null || r.getStack() == null || !r.getStack().isPinnedStack() ||
- r.getStack().isInStackLocked(r) == null) {
+ if (r == null || r.getStack() == null || !r.inPinnedWindowingMode()
+ || r.getStack().isInStackLocked(r) == null) {
return false;
}
@@ -8179,7 +8136,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// Only update the saved args from the args that are set
r.pictureInPictureArgs.copyOnlySet(params);
- if (r.getStack().getStackId() == PINNED_STACK_ID) {
+ if (r.inPinnedWindowingMode()) {
// If the activity is already in picture-in-picture, update the pinned stack now
// if it is not already expanding to fullscreen. Otherwise, the arguments will
// be used the next time the activity enters PiP
@@ -9800,35 +9757,12 @@ public class ActivityManagerService extends IActivityManager.Stub
public List<IBinder> getAppTasks(String callingPackage) {
int callingUid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
-
- synchronized(this) {
- ArrayList<IBinder> list = new ArrayList<IBinder>();
- try {
- if (DEBUG_ALL) Slog.v(TAG, "getAppTasks");
-
- final int N = mRecentTasks.size();
- for (int i = 0; i < N; i++) {
- TaskRecord tr = mRecentTasks.get(i);
- // Skip tasks that do not match the caller. We don't need to verify
- // callingPackage, because we are also limiting to callingUid and know
- // that will limit to the correct security sandbox.
- if (tr.effectiveUid != callingUid) {
- continue;
- }
- Intent intent = tr.getBaseIntent();
- if (intent == null ||
- !callingPackage.equals(intent.getComponent().getPackageName())) {
- continue;
- }
- ActivityManager.RecentTaskInfo taskInfo =
- createRecentTaskInfoFromTaskRecord(tr);
- AppTaskImpl taskImpl = new AppTaskImpl(taskInfo.persistentId, callingUid);
- list.add(taskImpl.asBinder());
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
+ try {
+ synchronized(this) {
+ return mRecentTasks.getAppTasksList(callingUid, callingPackage);
}
- return list;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -9851,58 +9785,6 @@ public class ActivityManagerService extends IActivityManager.Stub
return list;
}
- /**
- * Creates a new RecentTaskInfo from a TaskRecord.
- */
- private ActivityManager.RecentTaskInfo createRecentTaskInfoFromTaskRecord(TaskRecord tr) {
- // Update the task description to reflect any changes in the task stack
- tr.updateTaskDescription();
-
- // Compose the recent task info
- ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
- rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId;
- rti.persistentId = tr.taskId;
- rti.baseIntent = new Intent(tr.getBaseIntent());
- rti.origActivity = tr.origActivity;
- rti.realActivity = tr.realActivity;
- rti.description = tr.lastDescription;
- rti.stackId = tr.getStackId();
- rti.userId = tr.userId;
- rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
- rti.firstActiveTime = tr.firstActiveTime;
- rti.lastActiveTime = tr.lastActiveTime;
- rti.affiliatedTaskId = tr.mAffiliatedTaskId;
- rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
- rti.numActivities = 0;
- if (tr.mBounds != null) {
- rti.bounds = new Rect(tr.mBounds);
- }
- rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode();
- rti.resizeMode = tr.mResizeMode;
- rti.configuration.setTo(tr.getConfiguration());
-
- ActivityRecord base = null;
- ActivityRecord top = null;
- ActivityRecord tmp;
-
- for (int i = tr.mActivities.size() - 1; i >= 0; --i) {
- tmp = tr.mActivities.get(i);
- if (tmp.finishing) {
- continue;
- }
- base = tmp;
- if (top == null || (top.state == ActivityState.INITIALIZING)) {
- top = base;
- }
- rti.numActivities++;
- }
-
- rti.baseActivity = (base != null) ? base.intent.getComponent() : null;
- rti.topActivity = (top != null) ? top.intent.getComponent() : null;
-
- return rti;
- }
-
private boolean isGetTasksAllowed(String caller, int callingPid, int callingUid) {
boolean allowed = checkPermission(android.Manifest.permission.REAL_GET_TASKS,
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
@@ -9936,118 +9818,15 @@ public class ActivityManagerService extends IActivityManager.Stub
final int callingUid = Binder.getCallingUid();
userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
false, ALLOW_FULL_ONLY, "getRecentTasks", null);
+ final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(),
+ callingUid);
+ final boolean detailed = checkCallingPermission(
+ android.Manifest.permission.GET_DETAILED_TASKS)
+ == PackageManager.PERMISSION_GRANTED;
- final boolean includeProfiles = (flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0;
- final boolean withExcluded = (flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0;
synchronized (this) {
- final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(),
+ return mRecentTasks.getRecentTasks(maxNum, flags, allowed, detailed, userId,
callingUid);
- final boolean detailed = checkCallingPermission(
- android.Manifest.permission.GET_DETAILED_TASKS)
- == PackageManager.PERMISSION_GRANTED;
-
- if (!isUserRunning(userId, ActivityManager.FLAG_AND_UNLOCKED)) {
- Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents");
- return ParceledListSlice.emptyList();
- }
- mRecentTasks.loadUserRecentsLocked(userId);
-
- final int recentsCount = mRecentTasks.size();
- ArrayList<ActivityManager.RecentTaskInfo> res =
- new ArrayList<>(maxNum < recentsCount ? maxNum : recentsCount);
-
- final Set<Integer> includedUsers;
- if (includeProfiles) {
- includedUsers = mUserController.getProfileIds(userId);
- } else {
- includedUsers = new HashSet<>();
- }
- includedUsers.add(Integer.valueOf(userId));
-
- for (int i = 0; i < recentsCount && maxNum > 0; i++) {
- TaskRecord tr = mRecentTasks.get(i);
- // Only add calling user or related users recent tasks
- if (!includedUsers.contains(Integer.valueOf(tr.userId))) {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr);
- continue;
- }
-
- if (tr.realActivitySuspended) {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + tr);
- continue;
- }
-
- // Return the entry if desired by the caller. We always return
- // the first entry, because callers always expect this to be the
- // foreground app. We may filter others if the caller has
- // not supplied RECENT_WITH_EXCLUDED and there is some reason
- // we should exclude the entry.
-
- if (i == 0
- || withExcluded
- || (tr.intent == null)
- || ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
- == 0)) {
- if (!allowed) {
- // If the caller doesn't have the GET_TASKS permission, then only
- // allow them to see a small subset of tasks -- their own and home.
- if (!tr.isActivityTypeHome() && tr.effectiveUid != callingUid) {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr);
- continue;
- }
- }
- final ActivityStack stack = tr.getStack();
- if ((flags & ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS) != 0) {
- if (stack != null && stack.isHomeOrRecentsStack()) {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
- "Skipping, home or recents stack task: " + tr);
- continue;
- }
- }
- if ((flags & ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK) != 0) {
- if (stack != null && stack.isDockedStack() && stack.topTask() == tr) {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
- "Skipping, top task in docked stack: " + tr);
- continue;
- }
- }
- if ((flags & ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS) != 0) {
- if (stack != null && stack.isPinnedStack()) {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
- "Skipping, pinned stack task: " + tr);
- continue;
- }
- }
- if (tr.autoRemoveRecents && tr.getTopActivity() == null) {
- // Don't include auto remove tasks that are finished or finishing.
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
- "Skipping, auto-remove without activity: " + tr);
- continue;
- }
- if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0
- && !tr.isAvailable) {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
- "Skipping, unavail real act: " + tr);
- continue;
- }
-
- if (!tr.mUserSetupComplete) {
- // Don't include task launched while user is not done setting-up.
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
- "Skipping, user setup not complete: " + tr);
- continue;
- }
-
- ActivityManager.RecentTaskInfo rti = createRecentTaskInfoFromTaskRecord(tr);
- if (!detailed) {
- rti.baseIntent.replaceExtras((Bundle)null);
- }
-
- res.add(rti);
- maxNum--;
- }
- }
- return new ParceledListSlice<>(res);
}
}
@@ -10119,23 +9898,10 @@ public class ActivityManagerService extends IActivityManager.Stub
TaskRecord task = new TaskRecord(this,
mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
ainfo, intent, description);
-
- int trimIdx = mRecentTasks.trimForTaskLocked(task, false);
- if (trimIdx >= 0) {
- // If this would have caused a trim, then we'll abort because that
- // means it would be added at the end of the list but then just removed.
+ if (!mRecentTasks.addToBottom(task)) {
return INVALID_TASK_ID;
}
-
- final int N = mRecentTasks.size();
- if (N >= (ActivityManager.getMaxRecentTasksStatic()-1)) {
- final TaskRecord tr = mRecentTasks.remove(N - 1);
- tr.removedFromRecents();
- }
-
- task.inRecents = true;
- mRecentTasks.add(task);
- r.getStack().addTask(task, false, "addAppTask");
+ r.getStack().addTask(task, !ON_TOP, "addAppTask");
// TODO: Send the thumbnail to WM to store it.
@@ -10351,38 +10117,6 @@ public class ActivityManagerService extends IActivityManager.Stub
mWindowManager.executeAppTransition();
}
- private void removeTasksByPackageNameLocked(String packageName, int userId) {
- // Remove all tasks with activities in the specified package from the list of recent tasks
- for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
- TaskRecord tr = mRecentTasks.get(i);
- if (tr.userId != userId) continue;
-
- ComponentName cn = tr.intent.getComponent();
- if (cn != null && cn.getPackageName().equals(packageName)) {
- // If the package name matches, remove the task.
- mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
- }
- }
- }
-
- private void cleanupDisabledPackageTasksLocked(String packageName, Set<String> filterByClasses,
- int userId) {
-
- for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
- TaskRecord tr = mRecentTasks.get(i);
- if (userId != UserHandle.USER_ALL && tr.userId != userId) {
- continue;
- }
-
- ComponentName cn = tr.intent.getComponent();
- final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName)
- && (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
- if (sameComponent) {
- mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS);
- }
- }
- }
-
@Override
public void removeStack(int stackId) {
enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()");
@@ -10390,11 +10124,14 @@ public class ActivityManagerService extends IActivityManager.Stub
final long ident = Binder.clearCallingIdentity();
try {
final ActivityStack stack = mStackSupervisor.getStack(stackId);
- if (stack != null && !stack.isActivityTypeStandardOrUndefined()) {
+ if (stack == null) {
+ return;
+ }
+ if (!stack.isActivityTypeStandardOrUndefined()) {
throw new IllegalArgumentException(
"Removing non-standard stack is not allowed.");
}
- mStackSupervisor.removeStackLocked(stackId);
+ mStackSupervisor.removeStack(stack);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10609,7 +10346,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
final ActivityStack stack = r.getStack();
- if (stack == null || stack.mStackId != FREEFORM_WORKSPACE_STACK_ID) {
+ if (stack == null || !stack.inFreeformWindowingMode()) {
throw new IllegalStateException(
"exitFreeformMode: You can only go fullscreen from freeform.");
}
@@ -10677,27 +10414,20 @@ public class ActivityManagerService extends IActivityManager.Stub
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 */);
- }
- ActivityStack stack = mStackSupervisor.getStack(stackId);
+ final 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);
+ throw new IllegalStateException(
+ "moveTaskToStack: No stack for stackId=" + stackId);
}
if (!stack.isActivityTypeStandardOrUndefined()) {
throw new IllegalArgumentException("moveTaskToStack: Attempt to move task "
+ taskId + " to stack " + stackId);
}
+ if (stack.inSplitScreenPrimaryWindowingMode()) {
+ mWindowManager.setDockedStackCreateState(
+ DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
+ }
task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
"moveTaskToStack");
} finally {
@@ -10767,9 +10497,9 @@ public class ActivityManagerService extends IActivityManager.Stub
try {
synchronized (this) {
final ActivityStack stack =
- mStackSupervisor.getDefaultDisplay().getSplitScreenStack();
+ mStackSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
if (toTop) {
- mStackSupervisor.resizeStackLocked(stack.mStackId, null /* destBounds */,
+ mStackSupervisor.resizeStackLocked(stack, null /* destBounds */,
null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
true /* preserveWindows */, true /* allowResizeInDockedMode */,
!DEFER_RESUME);
@@ -10862,7 +10592,12 @@ public class ActivityManagerService extends IActivityManager.Stub
stack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds,
animationDuration, false /* fromFullscreen */);
} else {
- mStackSupervisor.resizeStackLocked(stackId, destBounds, null /* tempTaskBounds */,
+ final ActivityStack stack = mStackSupervisor.getStack(stackId);
+ if (stack == null) {
+ Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
+ return;
+ }
+ mStackSupervisor.resizeStackLocked(stack, destBounds, null /* tempTaskBounds */,
null /* tempTaskInsetBounds */, preserveWindows,
allowResizeInDockedMode, !DEFER_RESUME);
}
@@ -12938,6 +12673,10 @@ public class ActivityManagerService extends IActivityManager.Stub
throw new IllegalArgumentException("Provided bugreport type is not correct, value: "
+ bugreportType);
}
+ // Always log caller, even if it does not have permission to dump.
+ String type = extraOptions == null ? "bugreport" : extraOptions;
+ Slog.i(TAG, type + " requested by UID " + Binder.getCallingUid());
+
enforceCallingPermission(android.Manifest.permission.DUMP, "requestBugReport");
if (extraOptions != null) {
SystemProperties.set("dumpstate.options", extraOptions);
@@ -14145,7 +13884,6 @@ public class ActivityManagerService extends IActivityManager.Stub
// Load resources only after the current configuration has been set.
final Resources res = mContext.getResources();
- mHasRecents = res.getBoolean(com.android.internal.R.bool.config_hasRecents);
mThumbnailWidth = res.getDimensionPixelSize(
com.android.internal.R.dimen.thumbnail_width);
mThumbnailHeight = res.getDimensionPixelSize(
@@ -15104,10 +14842,31 @@ public class ActivityManagerService extends IActivityManager.Stub
long origId = Binder.clearCallingIdentity();
if (useProto) {
- //TODO: Options when dumping proto
final ProtoOutputStream proto = new ProtoOutputStream(fd);
- synchronized (this) {
- writeActivitiesToProtoLocked(proto);
+ String cmd = opti < args.length ? args[opti] : "";
+ opti++;
+
+ if ("activities".equals(cmd) || "a".equals(cmd)) {
+ // output proto is ActivityStackSupervisorProto
+ synchronized (this) {
+ writeActivitiesToProtoLocked(proto);
+ }
+ } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
+ // output proto is BroadcastProto
+ synchronized (this) {
+ writeBroadcastsToProtoLocked(proto);
+ }
+ } else {
+ // default option, dump everything, output is ActivityManagerServiceProto
+ synchronized (this) {
+ long activityToken = proto.start(ActivityManagerServiceProto.ACTIVITIES);
+ writeActivitiesToProtoLocked(proto);
+ proto.end(activityToken);
+
+ long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS);
+ writeBroadcastsToProtoLocked(proto);
+ proto.end(broadcastToken);
+ }
}
proto.flush();
Binder.restoreCallingIdentity(origId);
@@ -15133,7 +14892,9 @@ public class ActivityManagerService extends IActivityManager.Stub
}
} else if ("recents".equals(cmd) || "r".equals(cmd)) {
synchronized (this) {
- dumpRecentsLocked(fd, pw, args, opti, true, dumpPackage);
+ if (mRecentTasks != null) {
+ mRecentTasks.dump(pw, true /* dumpAll */, dumpPackage);
+ }
}
} else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
String[] newArgs;
@@ -15354,7 +15115,9 @@ public class ActivityManagerService extends IActivityManager.Stub
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- dumpRecentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+ if (mRecentTasks != null) {
+ mRecentTasks.dump(pw, dumpAll, dumpPackage);
+ }
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
@@ -15424,7 +15187,9 @@ public class ActivityManagerService extends IActivityManager.Stub
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- dumpRecentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+ if (mRecentTasks != null) {
+ mRecentTasks.dump(pw, dumpAll, dumpPackage);
+ }
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
@@ -15458,7 +15223,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
private void writeActivitiesToProtoLocked(ProtoOutputStream proto) {
- mStackSupervisor.writeToProto(proto, ACTIVITIES);
+ // The output proto of "activity --proto activities" is ActivityStackSupervisorProto
+ mStackSupervisor.writeToProto(proto);
}
private void dumpLastANRLocked(PrintWriter pw) {
@@ -15510,42 +15276,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- void dumpRecentsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpAll, String dumpPackage) {
- pw.println("ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)");
-
- boolean printedAnything = false;
-
- if (mRecentTasks != null && mRecentTasks.size() > 0) {
- boolean printedHeader = false;
-
- final int N = mRecentTasks.size();
- for (int i=0; i<N; i++) {
- TaskRecord tr = mRecentTasks.get(i);
- if (dumpPackage != null) {
- if (tr.realActivity == null ||
- !dumpPackage.equals(tr.realActivity.getPackageName())) {
- continue;
- }
- }
- if (!printedHeader) {
- pw.println(" Recent tasks:");
- printedHeader = true;
- printedAnything = true;
- }
- pw.print(" * Recent #"); pw.print(i); pw.print(": ");
- pw.println(tr);
- if (dumpAll) {
- mRecentTasks.get(i).dump(pw, " ");
- }
- }
- }
-
- if (!printedAnything) {
- pw.println(" (nothing)");
- }
- }
-
void dumpAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
pw.println("ACTIVITY MANAGER ASSOCIATIONS (dumpsys activity associations)");
@@ -16372,6 +16102,40 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
+ if (mRegisteredReceivers.size() > 0) {
+ Iterator it = mRegisteredReceivers.values().iterator();
+ while (it.hasNext()) {
+ ReceiverList r = (ReceiverList)it.next();
+ r.writeToProto(proto, BroadcastProto.RECEIVER_LIST);
+ }
+ }
+ mReceiverResolver.writeToProto(proto, BroadcastProto.RECEIVER_RESOLVER);
+ for (BroadcastQueue q : mBroadcastQueues) {
+ q.writeToProto(proto, BroadcastProto.BROADCAST_QUEUE);
+ }
+ for (int user=0; user<mStickyBroadcasts.size(); user++) {
+ long token = proto.start(BroadcastProto.STICKY_BROADCASTS);
+ proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
+ for (Map.Entry<String, ArrayList<Intent>> ent
+ : mStickyBroadcasts.valueAt(user).entrySet()) {
+ long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
+ proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
+ for (Intent intent : ent.getValue()) {
+ intent.writeToProto(proto, StickyBroadcastProto.StickyAction.INTENTS,
+ false, true, true, false);
+ }
+ proto.end(actionToken);
+ }
+ proto.end(token);
+ }
+
+ long handlerToken = proto.start(BroadcastProto.HANDLER);
+ proto.write(BroadcastProto.MainHandler.HANDLER, mHandler.toString());
+ mHandler.getLooper().writeToProto(proto, BroadcastProto.MainHandler.LOOPER);
+ proto.end(handlerToken);
+ }
+
void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
boolean needSep = false;
@@ -19360,7 +19124,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// Remove all permissions granted from/to this package
removeUriPermissionsForPackageLocked(ssp, userId, true);
- removeTasksByPackageNameLocked(ssp, userId);
+ mRecentTasks.removeTasksByPackageName(ssp, userId);
mServices.forceStopPackageLocked(ssp, userId);
@@ -20697,9 +20461,10 @@ public class ActivityManagerService extends IActivityManager.Stub
/** Helper method that requests bounds from WM and applies them to stack. */
private void resizeStackWithBoundsFromWindowManager(int stackId, boolean deferResume) {
final Rect newStackBounds = new Rect();
- mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration(newStackBounds);
+ final ActivityStack stack = mStackSupervisor.getStack(stackId);
+ stack.getBoundsForNewConfiguration(newStackBounds);
mStackSupervisor.resizeStackLocked(
- stackId, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */,
+ stack, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */,
null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
false /* preserveWindows */, false /* allowResizeInDockedMode */, deferResume);
}
@@ -24367,125 +24132,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
- * An implementation of IAppTask, that allows an app to manage its own tasks via
- * {@link android.app.ActivityManager.AppTask}. We keep track of the callingUid to ensure that
- * only the process that calls getAppTasks() can call the AppTask methods.
- */
- class AppTaskImpl extends IAppTask.Stub {
- private int mTaskId;
- private int mCallingUid;
-
- public AppTaskImpl(int taskId, int callingUid) {
- mTaskId = taskId;
- mCallingUid = callingUid;
- }
-
- private void checkCaller() {
- if (mCallingUid != Binder.getCallingUid()) {
- throw new SecurityException("Caller " + mCallingUid
- + " does not match caller of getAppTasks(): " + Binder.getCallingUid());
- }
- }
-
- @Override
- public void finishAndRemoveTask() {
- checkCaller();
-
- synchronized (ActivityManagerService.this) {
- long origId = Binder.clearCallingIdentity();
- try {
- // We remove the task from recents to preserve backwards
- if (!mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
- REMOVE_FROM_RECENTS)) {
- throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
- }
-
- @Override
- public ActivityManager.RecentTaskInfo getTaskInfo() {
- checkCaller();
-
- synchronized (ActivityManagerService.this) {
- long origId = Binder.clearCallingIdentity();
- try {
- TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(mTaskId);
- if (tr == null) {
- throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
- }
- return createRecentTaskInfoFromTaskRecord(tr);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
- }
-
- @Override
- public void moveToFront() {
- checkCaller();
- // Will bring task to front if it already has a root activity.
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (this) {
- mStackSupervisor.startActivityFromRecentsInner(mTaskId, null);
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
- @Override
- public int startActivity(IBinder whoThread, String callingPackage,
- Intent intent, String resolvedType, Bundle bOptions) {
- checkCaller();
-
- int callingUser = UserHandle.getCallingUserId();
- TaskRecord tr;
- IApplicationThread appThread;
- synchronized (ActivityManagerService.this) {
- tr = mStackSupervisor.anyTaskForIdLocked(mTaskId);
- if (tr == null) {
- throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
- }
- appThread = IApplicationThread.Stub.asInterface(whoThread);
- if (appThread == null) {
- throw new IllegalArgumentException("Bad app thread " + appThread);
- }
- }
- return mActivityStarter.startActivityMayWait(appThread, -1, callingPackage, intent,
- resolvedType, null, null, null, null, 0, 0, null, null,
- null, bOptions, false, callingUser, tr, "AppTaskImpl");
- }
-
- @Override
- public void setExcludeFromRecents(boolean exclude) {
- checkCaller();
-
- synchronized (ActivityManagerService.this) {
- long origId = Binder.clearCallingIdentity();
- try {
- TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(mTaskId);
- if (tr == null) {
- throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
- }
- Intent intent = tr.getBaseIntent();
- if (exclude) {
- intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- } else {
- intent.setFlags(intent.getFlags()
- & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
- }
- }
-
- /**
* Kill processes for the user with id userId and that depend on the package named packageName
*/
@Override
diff --git a/com/android/server/am/ActivityManagerShellCommand.java b/com/android/server/am/ActivityManagerShellCommand.java
index 4c934232..f03d2d53 100644
--- a/com/android/server/am/ActivityManagerShellCommand.java
+++ b/com/android/server/am/ActivityManagerShellCommand.java
@@ -73,10 +73,8 @@ import java.util.List;
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;
@@ -86,15 +84,6 @@ final class ActivityManagerShellCommand extends ShellCommand {
public static final String NO_CLASS_ERROR_CODE = "Error type 3";
private static final String SHELL_PACKAGE_NAME = "com.android.shell";
- // Is the object moving in a positive direction?
- private static final boolean MOVING_FORWARD = true;
- // Is the object moving in the horizontal plan?
- private static final boolean MOVING_HORIZONTALLY = true;
- // Is the object current point great then its target point?
- private static final boolean GREATER_THAN_TARGET = true;
- // Amount we reduce the stack size by when testing a task re-size.
- private static final int STACK_BOUNDS_INSET = 10;
-
// IPC interface to activity manager -- don't need to do additional security checks.
final IActivityManager mInterface;
@@ -1944,8 +1933,6 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runStackInfo(pw);
case "move-top-activity-to-pinned-stack":
return runMoveTopActivityToPinnedStack(pw);
- case "size-docked-stack-test":
- return runStackSizeDockedStackTest(pw);
case "remove":
return runStackRemove(pw);
default:
@@ -2143,89 +2130,6 @@ final class ActivityManagerShellCommand extends ShellCommand {
return 0;
}
- int runStackSizeDockedStackTest(PrintWriter pw) throws RemoteException {
- final PrintWriter err = getErrPrintWriter();
- final int stepSize = Integer.parseInt(getNextArgRequired());
- final String side = getNextArgRequired();
- final String delayStr = getNextArg();
- final int delayMs = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
-
- 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;
- }
- if (info.bounds == null) {
- err.println("Docked stack doesn't have a bounds");
- return -1;
- }
- Rect bounds = info.bounds;
-
- final boolean horizontalGrowth = "l".equals(side) || "r".equals(side);
- final int changeSize = (horizontalGrowth ? bounds.width() : bounds.height()) / 2;
- int currentPoint;
- switch (side) {
- case "l":
- currentPoint = bounds.left;
- break;
- case "r":
- currentPoint = bounds.right;
- break;
- case "t":
- currentPoint = bounds.top;
- break;
- case "b":
- currentPoint = bounds.bottom;
- break;
- default:
- err.println("Unknown growth side: " + side);
- return -1;
- }
-
- final int startPoint = currentPoint;
- final int minPoint = currentPoint - changeSize;
- final int maxPoint = currentPoint + changeSize;
-
- int maxChange;
- pw.println("Shrinking docked stack side=" + side);
- pw.flush();
- while (currentPoint > minPoint) {
- maxChange = Math.min(stepSize, currentPoint - minPoint);
- currentPoint -= maxChange;
- setBoundsSide(bounds, side, currentPoint);
- int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs);
- if (res < 0) {
- return res;
- }
- }
-
- pw.println("Growing docked stack side=" + side);
- pw.flush();
- while (currentPoint < maxPoint) {
- maxChange = Math.min(stepSize, maxPoint - currentPoint);
- currentPoint += maxChange;
- setBoundsSide(bounds, side, currentPoint);
- int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs);
- if (res < 0) {
- return res;
- }
- }
-
- pw.println("Back to Original size side=" + side);
- pw.flush();
- while (currentPoint > startPoint) {
- maxChange = Math.min(stepSize, currentPoint - startPoint);
- currentPoint -= maxChange;
- setBoundsSide(bounds, side, currentPoint);
- int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs);
- if (res < 0) {
- return res;
- }
- }
- return 0;
- }
-
void setBoundsSide(Rect bounds, String side, int value) {
switch (side) {
case "l":
@@ -2687,10 +2591,6 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" Change docked stack to <LEFT,TOP,RIGHT,BOTTOM>");
pw.println(" and supplying temporary different task bounds indicated by");
pw.println(" <TASK_LEFT,TOP,RIGHT,BOTTOM>");
- pw.println(" size-docked-stack-test: <STEP_SIZE> <l|t|r|b> [DELAY_MS]");
- pw.println(" Test command for sizing docked stack by");
- pw.println(" <STEP_SIZE> increments from the side <l>eft, <t>op, <r>ight, or <b>ottom");
- pw.println(" applying the optional [DELAY_MS] between each step.");
pw.println(" move-top-activity-to-pinned-stack: <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
pw.println(" Moves the top activity from");
pw.println(" <STACK_ID> to the pinned stack using <LEFT,TOP,RIGHT,BOTTOM> for the");
diff --git a/com/android/server/am/ActivityMetricsLogger.java b/com/android/server/am/ActivityMetricsLogger.java
index fdcb8c69..93c0f772 100644
--- a/com/android/server/am/ActivityMetricsLogger.java
+++ b/com/android/server/am/ActivityMetricsLogger.java
@@ -5,6 +5,7 @@ import static android.app.ActivityManager.START_TASK_TO_FRONT;
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;
@@ -127,7 +128,7 @@ class ActivityMetricsLogger {
case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
mWindowState = WINDOW_STATE_SIDE_BY_SIDE;
break;
- case WINDOW_STATE_FREEFORM:
+ case WINDOWING_MODE_FREEFORM:
mWindowState = WINDOW_STATE_FREEFORM;
break;
default:
diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java
index 7b0b942a..2c72a4db 100644
--- a/com/android/server/am/ActivityRecord.java
+++ b/com/android/server/am/ActivityRecord.java
@@ -17,9 +17,7 @@
package com.android.server.am;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-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;
import static android.app.ActivityManager.TaskDescription.ATTR_TASKDESCRIPTION_PREFIX;
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
@@ -60,6 +58,10 @@ import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
import static android.content.pm.ActivityInfo.PERSIST_ACROSS_REBOOTS;
import static android.content.pm.ActivityInfo.PERSIST_ROOT_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
@@ -284,6 +286,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
int configChangeFlags; // which config values have changed
private boolean keysPaused; // has key dispatching been paused for it?
int launchMode; // the launch mode activity attribute.
+ int lockTaskLaunchMode; // the lockTaskMode manifest attribute, subject to override
boolean visible; // does this activity's window need to be shown?
boolean visibleIgnoringKeyguard; // is this activity visible, ignoring the fact that Keyguard
// might hide this activity?
@@ -420,9 +423,13 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
if (iconFilename != null || taskDescription.getLabel() != null ||
taskDescription.getPrimaryColor() != 0) {
pw.print(prefix); pw.print("taskDescription:");
- pw.print(" iconFilename="); pw.print(taskDescription.getIconFilename());
pw.print(" label=\""); pw.print(taskDescription.getLabel());
pw.print("\"");
+ pw.print(" icon="); pw.print(taskDescription.getInMemoryIcon() != null
+ ? taskDescription.getInMemoryIcon().getByteCount() + " bytes"
+ : "null");
+ pw.print(" iconResource="); pw.print(taskDescription.getIconResource());
+ pw.print(" iconFilename="); pw.print(taskDescription.getIconFilename());
pw.print(" primaryColor=");
pw.println(Integer.toHexString(taskDescription.getPrimaryColor()));
pw.print(prefix + " backgroundColor=");
@@ -432,9 +439,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
pw.print(prefix + " navigationBarColor=");
pw.println(Integer.toHexString(taskDescription.getNavigationBarColor()));
}
- if (iconFilename == null && taskDescription.getIcon() != null) {
- pw.print(prefix); pw.println("taskDescription contains Bitmap");
- }
}
if (results != null) {
pw.print(prefix); pw.print("results="); pw.println(results);
@@ -661,8 +665,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
return;
}
- final boolean inPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID) &&
- (targetStackBounds != null);
+ final boolean inPictureInPictureMode = inPinnedWindowingMode() && targetStackBounds != null;
if (inPictureInPictureMode != mLastReportedPictureInPictureMode || forceUpdate) {
// Picture-in-picture mode changes also trigger a multi-window mode change as well, so
// update that here in order
@@ -684,10 +687,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
}
}
- boolean isFreeform() {
- return task != null && task.getStackId() == FREEFORM_WORKSPACE_STACK_ID;
- }
-
@Override
protected int getChildCount() {
// {@link ActivityRecord} is a leaf node and has no children.
@@ -831,23 +830,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
hasBeenLaunched = false;
mStackSupervisor = supervisor;
- mRotationAnimationHint = aInfo.rotationAnimation;
-
- if (options != null) {
- pendingOptions = options;
- mLaunchTaskBehind = pendingOptions.getLaunchTaskBehind();
-
- final int rotationAnimation = pendingOptions.getRotationAnimationHint();
- // Only override manifest supplied option if set.
- if (rotationAnimation >= 0) {
- mRotationAnimationHint = rotationAnimation;
- }
- PendingIntent usageReport = pendingOptions.getUsageTimeReport();
- if (usageReport != null) {
- appTimeTracker = new AppTimeTracker(usageReport);
- }
- }
-
// This starts out true, since the initial state of an activity is that we have everything,
// and we shouldn't never consider it lacking in state to be removed if it dies.
haveState = true;
@@ -914,6 +896,32 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
mShowWhenLocked = (aInfo.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
mTurnScreenOn = (aInfo.flags & FLAG_TURN_SCREEN_ON) != 0;
+
+ mRotationAnimationHint = aInfo.rotationAnimation;
+ lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
+ if (appInfo.isPrivilegedApp() && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
+ || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
+ lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+ }
+
+ if (options != null) {
+ pendingOptions = options;
+ mLaunchTaskBehind = options.getLaunchTaskBehind();
+
+ final int rotationAnimation = pendingOptions.getRotationAnimationHint();
+ // Only override manifest supplied option if set.
+ if (rotationAnimation >= 0) {
+ mRotationAnimationHint = rotationAnimation;
+ }
+ final PendingIntent usageReport = pendingOptions.getUsageTimeReport();
+ if (usageReport != null) {
+ appTimeTracker = new AppTimeTracker(usageReport);
+ }
+ final boolean useLockTask = pendingOptions.getLockTaskMode();
+ if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
+ lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+ }
+ }
}
AppWindowContainerController getWindowContainerController() {
@@ -948,7 +956,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
// update the initial multi-window modes so that the callbacks are scheduled correctly when
// the user leaves that mode.
mLastReportedMultiWindowMode = !task.mFullscreen;
- mLastReportedPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID);
+ mLastReportedPictureInPictureMode = inPinnedWindowingMode();
}
void removeWindowContainer() {
@@ -1551,7 +1559,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
// 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.
final ActivityDisplay display = getDisplay();
- boolean hasSplitScreenStack = display != null && display.hasSplitScreenStack();
+ boolean hasSplitScreenStack = display != null && display.hasSplitScreenPrimaryStack();
isVisible = hasSplitScreenStack || mStackSupervisor.isFocusedStack(getStack());
}
@@ -2739,6 +2747,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
void setShowWhenLocked(boolean showWhenLocked) {
mShowWhenLocked = showWhenLocked;
+ mStackSupervisor.ensureActivitiesVisibleLocked(null, 0 /* configChanges */,
+ false /* preserveWindows */);
}
/**
diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java
index 1940ca2b..f0811dda 100644
--- a/com/android/server/am/ActivityStack.java
+++ b/com/android/server/am/ActivityStack.java
@@ -16,27 +16,25 @@
package com.android.server.am;
-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.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_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.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
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;
@@ -100,7 +98,6 @@ import static java.lang.Integer.MAX_VALUE;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IActivityController;
@@ -110,6 +107,7 @@ 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;
@@ -192,10 +190,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// finished destroying itself.
private static final int DESTROY_TIMEOUT = 10 * 1000;
- // How long until we reset a task when the user returns to it. Currently
- // disabled.
- private static final long ACTIVITY_INACTIVE_RESET_TIME = 0;
-
// Set to false to disable the preview that is shown while a new activity
// is being started.
private static final boolean SHOW_APP_STARTING_PREVIEW = true;
@@ -352,12 +346,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
private final Rect mTmpRect2 = new Rect();
+ private final Point mTmpSize = new Point();
/** Run all ActivityStacks through this */
protected final ActivityStackSupervisor mStackSupervisor;
- private final LaunchingTaskPositioner mTaskPositioner;
-
private boolean mTopActivityOccludesKeyguard;
private ActivityRecord mTopDismissingKeyguardActivity;
@@ -460,8 +453,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
mWindowManager = mService.mWindowManager;
mStackId = stackId;
mCurrentUser = mService.mUserController.getCurrentUserId();
- mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
- ? new LaunchingTaskPositioner() : null;
mTmpRect2.setEmpty();
setWindowingMode(windowingMode);
setActivityType(activityType);
@@ -479,6 +470,16 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return mWindowContainerController;
}
+ @Override
+ public void onConfigurationChanged(Configuration newParentConfig) {
+ final int prevWindowingMode = getWindowingMode();
+ super.onConfigurationChanged(newParentConfig);
+ final ActivityDisplay display = getDisplay();
+ if (display != null && prevWindowingMode != getWindowingMode()) {
+ display.onStackWindowingModeChanged(this);
+ }
+ }
+
/** Adds the stack to specified display and calls WindowManager to do the same. */
void reparent(ActivityDisplay activityDisplay, boolean onTop) {
removeFromDisplay();
@@ -502,14 +503,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
mDisplayId = activityDisplay.mDisplayId;
mBounds = bounds != null ? new Rect(bounds) : null;
mFullscreen = mBounds == null;
- if (mTaskPositioner != null) {
- mTaskPositioner.setDisplay(activityDisplay.mDisplay);
- mTaskPositioner.configure(mBounds);
- }
+
onParentChanged();
activityDisplay.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM);
- if (mStackId == DOCKED_STACK_ID) {
+ if (inSplitScreenPrimaryWindowingMode()) {
// If we created a docked stack we want to resize it so it resizes all other stacks
// in the system.
mStackSupervisor.resizeDockedStackLocked(
@@ -533,9 +531,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
display.removeChild(this);
}
mDisplayId = INVALID_DISPLAY;
- if (mTaskPositioner != null) {
- mTaskPositioner.reset();
- }
}
/** Removes the stack completely. Also calls WindowManager to do the same on its side. */
@@ -639,9 +634,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
void setBounds(Rect bounds) {
mBounds = mFullscreen ? null : new Rect(bounds);
- if (mTaskPositioner != null) {
- mTaskPositioner.configure(bounds);
- }
}
ActivityRecord topRunningActivityLocked() {
@@ -818,14 +810,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return isActivityTypeHome() || isActivityTypeRecents();
}
- final boolean isDockedStack() {
- return mStackId == DOCKED_STACK_ID;
- }
-
- final boolean isPinnedStack() {
- return mStackId == PINNED_STACK_ID;
- }
-
final boolean isOnHomeDisplay() {
return mDisplayId == DEFAULT_DISPLAY;
}
@@ -1505,9 +1489,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
* needed. A stack is considered translucent if it don't contain a visible or
* starting (about to be visible) activity that is fullscreen (opaque).
* @param starting The currently starting activity or null if there is none.
- * @param stackBehindId The id of the stack directly behind this one.
+ * @param stackBehind The stack directly behind this one.
*/
- private boolean isStackTranslucent(ActivityRecord starting, int stackBehindId) {
+ private boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1532,7 +1516,6 @@ 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()
@@ -1553,6 +1536,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
&& !mForceHidden;
}
+ boolean isTopStackOnDisplay() {
+ return getDisplay().isTopStack(this);
+ }
+
/**
* Returns true if the stack should be visible.
*
@@ -1563,23 +1550,15 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return false;
}
- if (mStackSupervisor.isFrontStackOnDisplay(this) || mStackSupervisor.isFocusedStack(this)) {
+ final ActivityDisplay display = getDisplay();
+ if (isTopStackOnDisplay() || mStackSupervisor.isFocusedStack(this)) {
return true;
}
- final ActivityDisplay display = getDisplay();
- final ArrayList<ActivityStack> displayStacks = display.mStacks;
- final int stackIndex = displayStacks.indexOf(this);
-
- if (stackIndex == displayStacks.size() - 1) {
- Slog.wtf(TAG,
- "Stack=" + this + " isn't front stack but is at the top of the stack list");
- return false;
- }
+ final int stackIndex = display.getIndexOf(this);
// 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 ActivityStack topStack = getDisplay().getTopStack();
final int windowingMode = getWindowingMode();
final int activityType = getActivityType();
@@ -1587,7 +1566,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// 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);
+ return topStack.isStackTranslucent(starting, this);
}
return true;
}
@@ -1596,34 +1575,31 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// 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 (activityType == ACTIVITY_TYPE_HOME) {
- final ActivityStack splitScreenStack = display.getStack(
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
- int dockedStackIndex = displayStacks.indexOf(splitScreenStack);
+ final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack();
+ int dockedStackIndex = display.getIndexOf(splitScreenStack);
if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) {
return false;
}
}
// Find the first stack behind front stack that actually got something visible.
- int stackBehindTopIndex = displayStacks.indexOf(topStack) - 1;
+ int stackBehindTopIndex = display.getIndexOf(topStack) - 1;
while (stackBehindTopIndex >= 0 &&
- displayStacks.get(stackBehindTopIndex).topRunningActivityLocked() == null) {
+ display.getChildAt(stackBehindTopIndex).topRunningActivityLocked() == null) {
stackBehindTopIndex--;
}
final ActivityStack stackBehindTop = (stackBehindTopIndex >= 0)
- ? displayStacks.get(stackBehindTopIndex) : null;
- int stackBehindTopId = INVALID_STACK_ID;
+ ? display.getChildAt(stackBehindTopIndex) : null;
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 (topStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || alwaysOnTop) {
- if (stackIndex == stackBehindTopIndex) {
+ if (this == stackBehindTop) {
// Stacks directly behind the docked or pinned stack are always visible.
return true;
} else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) {
@@ -1632,14 +1608,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
if (stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
return true;
} else if (stackBehindTopActivityType == ACTIVITY_TYPE_ASSISTANT) {
- return displayStacks.get(stackBehindTopIndex).isStackTranslucent(
- starting, mStackId);
+ return stackBehindTop.isStackTranslucent(starting, this);
}
}
}
if (topStack.isBackdropToTranslucentActivity()
- && topStack.isStackTranslucent(starting, stackBehindTopId)) {
+ && topStack.isStackTranslucent(starting, stackBehindTop)) {
// 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
@@ -1657,14 +1632,15 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
}
- if (StackId.isStaticStack(mStackId)
- || isHomeOrRecentsStack() || isActivityTypeAssistant()) {
- // Visibility of any static stack should have been determined by the conditions above.
+ if (isOnHomeDisplay()) {
+ // Visibility of any stack on default display should have been determined by the
+ // conditions above.
return false;
}
- for (int i = stackIndex + 1; i < displayStacks.size(); i++) {
- final ActivityStack stack = displayStacks.get(i);
+ final int stackCount = display.getChildCount();
+ for (int i = stackIndex + 1; i < stackCount; i++) {
+ final ActivityStack stack = display.getChildAt(i);
if (!stack.mFullscreen && !stack.hasFullscreenTask()) {
continue;
@@ -1675,7 +1651,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return false;
}
- if (!stack.isStackTranslucent(starting, INVALID_STACK_ID)) {
+ if (!stack.isStackTranslucent(starting, null /* stackBehind */)) {
return false;
}
}
@@ -1801,7 +1777,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
makeInvisible(r);
}
}
- if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+ final int windowingMode = getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_FREEFORM) {
// 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.
@@ -1816,7 +1793,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// show activities in the next application stack behind them vs. another
// task in the home stack like recents.
behindFullscreenActivity = true;
- } else if (mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+ } else if (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping after task=" + task
+ " returning to non-application type=" + task.getTaskToReturnTo());
// Once we reach a fullscreen stack task that has a running activity and should
@@ -1852,6 +1830,32 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
/**
+ * Returns true if this stack should be resized to match the bounds specified by
+ * {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack.
+ */
+ boolean resizeStackWithLaunchBounds() {
+ return inPinnedWindowingMode();
+ }
+
+ /**
+ * Returns true if we try to maintain focus in the current stack when the top activity finishes.
+ */
+ private boolean keepFocusInStackIfPossible() {
+ final int windowingMode = getWindowingMode();
+ return windowingMode == WINDOWING_MODE_FREEFORM
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || windowingMode == WINDOWING_MODE_PINNED;
+ }
+
+ /**
+ * Returns true if the top task in the task is allowed to return home when finished and
+ * there are other tasks in the stack.
+ */
+ boolean allowTopTaskToReturnHome() {
+ return !inPinnedWindowingMode();
+ }
+
+ /**
* @return the top most visible activity that wants to dismiss Keyguard
*/
ActivityRecord getTopDismissingKeyguardActivity() {
@@ -1867,7 +1871,7 @@ 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 isInPinnedStack = r.inPinnedWindowingMode();
final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing(
mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
final boolean keyguardLocked = mStackSupervisor.mKeyguardController.isKeyguardLocked();
@@ -2165,7 +2169,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
mResumedActivity = r;
r.state = ActivityState.RESUMED;
mService.setResumedActivityUncheckLocked(r, reason);
- mStackSupervisor.addRecentActivity(r);
+ mStackSupervisor.mRecentTasks.add(r.getTask());
}
private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
@@ -2572,8 +2576,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
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)) {
+ } else if (SHOW_APP_STARTING_PREVIEW && lastStack != null
+ && lastStack.isTopStackOnDisplay()) {
next.showStartingWindow(null /* prev */, false /* newTask */,
false /* taskSwitch */);
}
@@ -2617,7 +2621,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev,
ActivityOptions options, String reason) {
- if ((!mFullscreen || !isOnHomeDisplay()) && adjustFocusToNextFocusableStackLocked(reason)) {
+ if (adjustFocusToNextFocusableStackLocked(reason)) {
// Try to move focus to the next visible stack with a running activity if this
// stack is not covering the entire screen or is on a secondary display (with no home
// stack).
@@ -2746,9 +2750,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// make underlying task focused when this one will be finished.
int returnToType = isLastTaskOverHome
? task.getTaskToReturnTo() : ACTIVITY_TYPE_STANDARD;
- if (fromHomeOrRecents && StackId.allowTopTaskToReturnHome(mStackId)) {
- returnToType = topTask == null
- ? ACTIVITY_TYPE_HOME : topTask.getActivityType();
+ if (fromHomeOrRecents && allowTopTaskToReturnHome()) {
+ returnToType = topTask == null ? ACTIVITY_TYPE_HOME : topTask.getActivityType();
}
task.setTaskToReturnTo(returnToType);
}
@@ -2901,7 +2904,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// Ensure the caller has requested not to trigger auto-enter PiP
return false;
}
- if (pipCandidate == null || pipCandidate.getStackId() == PINNED_STACK_ID) {
+ if (pipCandidate == null || pipCandidate.inPinnedWindowingMode()) {
// Ensure that we do not trigger entering PiP an activity on the pinned stack
return false;
}
@@ -3186,15 +3189,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop,
ActivityRecord newActivity) {
- boolean forceReset =
+ final boolean forceReset =
(newActivity.info.flags & ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0;
- if (ACTIVITY_INACTIVE_RESET_TIME > 0
- && taskTop.getTask().getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) {
- if ((newActivity.info.flags & ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) {
- forceReset = true;
- }
- }
-
final TaskRecord task = taskTop.getTask();
/** False until we evaluate the TaskRecord associated with taskTop. Switches to true
@@ -3291,7 +3287,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
final String myReason = reason + " adjustFocus";
if (next != r) {
- if (next != null && StackId.keepFocusInStackIfPossible(mStackId) && isFocusable()) {
+ if (next != null && keepFocusInStackIfPossible() && isFocusable()) {
// For freeform, docked, and pinned stacks we always keep the focus within the
// stack as long as there is a running activity.
return;
@@ -3757,7 +3753,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
if (mode == FINISH_IMMEDIATELY
|| (prevState == ActivityState.PAUSED
- && (mode == FINISH_AFTER_PAUSE || mStackId == PINNED_STACK_ID))
+ && (mode == FINISH_AFTER_PAUSE || inPinnedWindowingMode()))
|| finishingActivityInNonFocusedStack
|| prevState == STOPPING
|| prevState == STOPPED
@@ -4436,7 +4432,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
AppTimeTracker timeTracker, String reason) {
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr);
- final ActivityStack topStack = getTopStackOnDisplay();
+ final ActivityStack topStack = getDisplay().getTopStack();
final ActivityRecord topActivity = topStack != null ? topStack.topActivity() : null;
final int numTasks = mTaskHistory.size();
final int index = mTaskHistory.indexOf(tr);
@@ -4464,7 +4460,9 @@ 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()) {
- mStackSupervisor.addRecentActivity(top);
+ if (top != null) {
+ mStackSupervisor.mRecentTasks.add(top.getTask());
+ }
ActivityOptions.abort(options);
return;
}
@@ -4524,7 +4522,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// If we have a watcher, preflight the move before committing to it. First check
// for *other* available tasks, but if none are available, then try again allowing the
// current task to be selected.
- if (mStackSupervisor.isFrontStackOnDisplay(this) && mService.mController != null) {
+ if (isTopStackOnDisplay() && mService.mController != null) {
ActivityRecord next = topRunningActivityLocked(null, taskId);
if (next == null) {
next = topRunningActivityLocked(null, 0);
@@ -4571,8 +4569,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController());
}
- if (mStackId == PINNED_STACK_ID) {
- mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
+ if (inPinnedWindowingMode()) {
+ mStackSupervisor.removeStack(this);
return true;
}
@@ -4604,15 +4602,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return true;
}
- /**
- * Get the topmost stack on the current display. It may be different from focused stack, because
- * focus may be on another display.
- */
- private ActivityStack getTopStackOnDisplay() {
- final ArrayList<ActivityStack> stacks = getDisplay().mStacks;
- return stacks.isEmpty() ? null : stacks.get(stacks.size() - 1);
- }
-
static void logStartActivity(int tag, ActivityRecord r, TaskRecord task) {
final Uri data = r.intent.getData();
final String strData = data != null ? data.toSafeString() : null;
@@ -4687,7 +4676,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
final TaskRecord task = mTaskHistory.get(i);
if (task.isResizeable()) {
- if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+ if (inFreeformWindowingMode()) {
// For freeform stack we don't adjust the size of the tasks to match that
// of the stack, but we do try to make sure the tasks are still contained
// with the bounds of the stack.
@@ -4889,7 +4878,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
if (focusedStack && topTask) {
// Give the latest time to ensure foreground task can be sorted
// at the first, because lastActiveTime of creating task is 0.
- ci.lastActiveTime = System.currentTimeMillis();
+ ci.lastActiveTime = SystemClock.elapsedRealtime();
topTask = false;
}
@@ -5069,7 +5058,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.
- mStackSupervisor.removeTaskFromRecents(task);
+ mStackSupervisor.mRecentTasks.remove(task);
}
task.removeWindowContainer();
@@ -5097,7 +5086,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
task.setStack(null);
// Notify if a task from the pinned stack is being removed (or moved depending on the mode)
- if (mStackId == PINNED_STACK_ID) {
+ if (inPinnedWindowingMode()) {
mService.mTaskChangeNotificationController.notifyActivityUnpinned();
}
}
@@ -5120,10 +5109,12 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
boolean layoutTaskInStack(TaskRecord task, ActivityInfo.WindowLayout windowLayout) {
- if (mTaskPositioner == null) {
+ if (!task.inFreeformWindowingMode()) {
return false;
}
- mTaskPositioner.updateDefaultBounds(task, mTaskHistory, windowLayout);
+ mStackSupervisor.getLaunchingTaskPositioner()
+ .updateDefaultBounds(task, mTaskHistory, windowLayout);
+
return true;
}
@@ -5248,10 +5239,12 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
@Override
public String toString() {
return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this))
- + " stackId=" + mStackId + ", " + mTaskHistory.size() + " tasks}";
+ + " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType())
+ + " mode=" + windowingModeToString(getWindowingMode()) + ", "
+ + mTaskHistory.size() + " tasks}";
}
- void onLockTaskPackagesUpdatedLocked() {
+ void onLockTaskPackagesUpdated() {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
mTaskHistory.get(taskNdx).setLockTaskAuth();
}
diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java
index da2827a6..5c91e3cc 100644
--- a/com/android/server/am/ActivityStackSupervisor.java
+++ b/com/android/server/am/ActivityStackSupervisor.java
@@ -21,11 +21,7 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.START_ANY_ACTIVITY;
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.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.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
@@ -35,10 +31,13 @@ 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_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.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
@@ -47,6 +46,7 @@ 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.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;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
@@ -86,12 +86,13 @@ 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.CONFIGURATION_CONTAINER;
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;
@@ -102,7 +103,6 @@ import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityManager.StackId;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityManagerInternal.SleepToken;
import android.app.ActivityOptions;
@@ -176,7 +176,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
-public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener {
+public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener,
+ RecentTasks.Callbacks {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM;
private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
private static final String TAG_IDLE = TAG + POSTFIX_IDLE;
@@ -286,7 +287,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
final ActivityManagerService mService;
- private RecentTasks mRecentTasks;
+ RecentTasks mRecentTasks;
final ActivityStackSupervisorHandler mHandler;
@@ -294,8 +295,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
WindowManagerService mWindowManager;
DisplayManager mDisplayManager;
+ LaunchingTaskPositioner mTaskPositioner = new LaunchingTaskPositioner();
+
/** Counter for next free stack ID to use for dynamic activity stacks. */
- private int mNextFreeStackId = FIRST_DYNAMIC_STACK_ID;
+ private int mNextFreeStackId = 0;
/**
* Maps the task identifier that activities are currently being started in to the userId of the
@@ -576,6 +579,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void setRecentTasks(RecentTasks recentTasks) {
mRecentTasks = recentTasks;
+ mRecentTasks.registerCallback(this);
}
/**
@@ -627,15 +631,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return stack != null && stack == mFocusedStack;
}
- /** The top most stack on its display. */
- boolean isFrontStackOnDisplay(ActivityStack stack) {
- return isFrontOfStackList(stack, stack.getDisplay().mStacks);
- }
-
- private boolean isFrontOfStackList(ActivityStack stack, List<ActivityStack> stackList) {
- return stack == stackList.get((stackList.size() - 1));
- }
-
/** NOTE: Should only be called from {@link ActivityStack#moveToFront} */
void setFocusStackUnchecked(String reason, ActivityStack focusCandidate) {
if (!focusCandidate.isFocusable()) {
@@ -731,9 +726,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
int numDisplays = mActivityDisplays.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
- ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
final TaskRecord task = stack.taskForIdLocked(id);
if (task != null) {
return task;
@@ -749,7 +744,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// Otherwise, check the recent tasks and return if we find it there and we are not restoring
// the task from recents
if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents");
- final TaskRecord task = mRecentTasks.taskForIdLocked(id);
+ final TaskRecord task = mRecentTasks.getTask(id);
if (task == null) {
if (DEBUG_RECENTS) {
@@ -776,9 +771,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
ActivityRecord isInAnyStackLocked(IBinder token) {
int numDisplays = mActivityDisplays.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
- ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityRecord r = stacks.get(stackNdx).isInStackLocked(token);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ final ActivityRecord r = stack.isInStackLocked(token);
if (r != null) {
return r;
}
@@ -816,18 +812,21 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void lockAllProfileTasks(@UserIdInt int userId) {
mWindowManager.deferSurfaceLayout();
try {
- final List<ActivityStack> stacks = getStacks();
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; stackNdx--) {
- final List<TaskRecord> tasks = stacks.get(stackNdx).getAllTasks();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; taskNdx--) {
- final TaskRecord task = tasks.get(taskNdx);
-
- // Check the task for a top activity belonging to userId, or returning a result
- // to an activity belonging to userId. Example case: a document picker for
- // personal files, opened by a work app, should still get locked.
- if (taskTopActivityIsUser(task, userId)) {
- mService.mTaskChangeNotificationController.notifyTaskProfileLocked(
- task.taskId, userId);
+ for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ final List<TaskRecord> tasks = stack.getAllTasks();
+ for (int taskNdx = tasks.size() - 1; taskNdx >= 0; taskNdx--) {
+ final TaskRecord task = tasks.get(taskNdx);
+
+ // Check the task for a top activity belonging to userId, or returning a
+ // result to an activity belonging to userId. Example case: a document
+ // picker for personal files, opened by a work app, should still get locked.
+ if (taskTopActivityIsUser(task, userId)) {
+ mService.mTaskChangeNotificationController.notifyTaskProfileLocked(
+ task.taskId, userId);
+ }
}
}
}
@@ -858,7 +857,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// [u*MAX_TASK_IDS_PER_USER, (u+1)*MAX_TASK_IDS_PER_USER-1], so if MAX_TASK_IDS_PER_USER
// 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)
+ while (mRecentTasks.containsTaskId(candidateTaskId, userId)
|| anyTaskForIdLocked(
candidateTaskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
candidateTaskId = nextTaskIdForUser(candidateTaskId, userId);
@@ -893,9 +892,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
final String processName = app.processName;
boolean didSomething = false;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
if (!isFocusedStack(stack)) {
continue;
}
@@ -928,9 +927,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
boolean allResumedActivitiesIdle() {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
if (!isFocusedStack(stack) || stack.numActivities() == 0) {
continue;
}
@@ -949,9 +948,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
boolean allResumedActivitiesComplete() {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
if (isFocusedStack(stack)) {
final ActivityRecord r = stack.mResumedActivity;
if (r != null && r.state != RESUMED) {
@@ -968,12 +967,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return true;
}
- boolean allResumedActivitiesVisible() {
+ private boolean allResumedActivitiesVisible() {
boolean foundResumed = false;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
final ActivityRecord r = stack.mResumedActivity;
if (r != null) {
if (!r.nowVisible || mActivitiesWaitingForVisibleActivity.contains(r)) {
@@ -997,9 +996,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) {
boolean someActivityPaused = false;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
if (!isFocusedStack(stack) && stack.mResumedActivity != null) {
if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
" mResumedActivity=" + stack.mResumedActivity);
@@ -1014,9 +1013,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
boolean allPausedActivitiesComplete() {
boolean pausing = true;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
final ActivityRecord r = stack.mPausingActivity;
if (r != null && r.state != PAUSED && r.state != STOPPED && r.state != STOPPING) {
if (DEBUG_STATES) {
@@ -1034,9 +1033,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void cancelInitializingActivities() {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- stacks.get(stackNdx).cancelInitializingActivities();
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ stack.cancelInitializingActivities();
}
}
}
@@ -1134,13 +1134,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) {
final int displayId = mTmpOrderedDisplayIds.get(i);
- final List<ActivityStack> stacks = mActivityDisplays.get(displayId).mStacks;
- if (stacks == null) {
- continue;
- }
- for (int j = stacks.size() - 1; j >= 0; --j) {
- final ActivityStack stack = stacks.get(j);
- if (stack != focusedStack && isFrontStackOnDisplay(stack) && stack.isFocusable()) {
+ final ActivityDisplay display = mActivityDisplays.get(displayId);
+ for (int j = display.getChildCount() - 1; j >= 0; --j) {
+ final ActivityStack stack = display.getChildAt(j);
+ if (stack != focusedStack && stack.isTopStackOnDisplay() && stack.isFocusable()) {
r = stack.topRunningActivityLocked();
if (r != null) {
return r;
@@ -1156,9 +1153,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
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;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<>();
runningTaskLists.add(stackTaskList);
stack.getTasksLocked(stackTaskList, callingUid, allowed);
@@ -1607,6 +1604,16 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
+ // Check if someone tries to launch an unwhitelisted activity into LockTask mode.
+ final boolean lockTaskMode = options.getLockTaskMode();
+ if (lockTaskMode && !mService.mLockTaskController.isPackageWhitelisted(
+ UserHandle.getUserId(callingUid), aInfo.packageName)) {
+ final String msg = "Permission Denial: starting " + intent.toString()
+ + " from " + callerApp + " (pid=" + callingPid
+ + ", uid=" + callingUid + ") with lockTaskMode=true";
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
}
return true;
@@ -1928,9 +1935,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
boolean handleAppDiedLocked(ProcessRecord app) {
boolean hasVisibleActivities = false;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- hasVisibleActivities |= stacks.get(stackNdx).handleAppDiedLocked(app);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ hasVisibleActivities |= stack.handleAppDiedLocked(app);
}
}
return hasVisibleActivities;
@@ -1938,9 +1946,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void closeSystemDialogsLocked() {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- stacks.get(stackNdx).closeSystemDialogsLocked();
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ stack.closeSystemDialogsLocked();
}
}
}
@@ -1966,9 +1975,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
boolean doit, boolean evenPersistent, int userId) {
boolean didSomething = false;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
if (stack.finishDisabledPackageActivitiesLocked(
packageName, filterByClasses, doit, evenPersistent, userId)) {
didSomething = true;
@@ -1988,9 +1997,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// hosted by the process that is actually still the foreground.
ProcessRecord fgApp = null;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
if (isFocusedStack(stack)) {
if (stack.mResumedActivity != null) {
fgApp = stack.mResumedActivity.app;
@@ -2040,9 +2049,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void updateActivityApplicationInfoLocked(ApplicationInfo aInfo) {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- stacks.get(stackNdx).updateActivityApplicationInfoLocked(aInfo);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ stack.updateActivityApplicationInfoLocked(aInfo);
}
}
}
@@ -2051,10 +2061,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
TaskRecord finishedTask = null;
ActivityStack focusedStack = getFocusedStack();
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- final int numStacks = stacks.size();
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ final int numStacks = display.getChildCount();
for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityStack stack = display.getChildAt(stackNdx);
TaskRecord t = stack.finishTopRunningActivityLocked(app, reason);
if (stack == focusedStack || finishedTask == null) {
finishedTask = t;
@@ -2066,10 +2076,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void finishVoiceTask(IVoiceInteractionSession session) {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- final int numStacks = stacks.size();
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ final int numStacks = display.getChildCount();
for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityStack stack = display.getChildAt(stackNdx);
stack.finishVoiceTask(session);
}
}
@@ -2105,8 +2115,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// 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 */,
+ if (stack.resizeStackWithLaunchBounds()) {
+ resizeStackLocked(stack, bounds, null /* tempTaskBounds */,
null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
true /* allowResizeInDockedMode */, !DEFER_RESUME);
} else {
@@ -2125,7 +2135,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
"findTaskToMoveToFront: moved to front of stack=" + currentStack);
handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY,
- currentStack.mStackId, forceNonResizeable);
+ currentStack, forceNonResizeable);
}
boolean canUseActivityOptionsLaunchBounds(ActivityOptions options) {
@@ -2139,6 +2149,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
|| mService.mSupportsFreeformWindowManagement;
}
+ LaunchingTaskPositioner getLaunchingTaskPositioner() {
+ return mTaskPositioner;
+ }
+
protected <T extends ActivityStack> T getStack(int stackId) {
for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
final T stack = mActivityDisplays.valueAt(i).getStack(stackId);
@@ -2316,8 +2330,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
if (display != null) {
- for (int i = display.mStacks.size() - 1; i >= 0; --i) {
- stack = (T) display.mStacks.get(i);
+ for (int i = display.getChildCount() - 1; i >= 0; --i) {
+ stack = (T) display.getChildAt(i);
if (stack.isCompatible(windowingMode, activityType)) {
return stack;
}
@@ -2385,8 +2399,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
// 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);
+ for (int i = activityDisplay.getChildCount() - 1; i >= 0; --i) {
+ final ActivityStack stack = activityDisplay.getChildAt(i);
if (isValidLaunchStack(stack, displayId, r)) {
return stack;
}
@@ -2417,25 +2431,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return r.supportsSplitScreenWindowingMode();
}
- if (StackId.isDynamicStack(stack.mStackId)) {
+ if (!stack.isOnHomeDisplay()) {
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) {
- allStacks.addAll(mActivityDisplays.valueAt(displayNdx).mStacks);
- }
- return allStacks;
- }
-
- ArrayList<ActivityStack> getStacksOnDefaultDisplay() {
- return mActivityDisplays.valueAt(DEFAULT_DISPLAY).mStacks;
- }
-
/**
* Get next focusable stack in the system. This will search across displays and stacks
* in last-focused order for a focusable and visible stack, different from the target stack.
@@ -2450,10 +2452,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) {
final int displayId = mTmpOrderedDisplayIds.get(i);
// If a display is registered in WM, it must also be available in AM.
- @SuppressWarnings("ConstantConditions")
- final List<ActivityStack> stacks = getActivityDisplayOrCreateLocked(displayId).mStacks;
- for (int j = stacks.size() - 1; j >= 0; --j) {
- final ActivityStack stack = stacks.get(j);
+ final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
+ for (int j = display.getChildCount() - 1; j >= 0; --j) {
+ final ActivityStack stack = display.getChildAt(j);
if (stack != currentFocus && stack.isFocusable()
&& stack.shouldBeVisible(null)) {
return stack;
@@ -2511,20 +2512,17 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return null;
}
- void resizeStackLocked(int stackId, Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds,
- boolean preserveWindows, boolean allowResizeInDockedMode, boolean deferResume) {
- if (stackId == DOCKED_STACK_ID) {
+ void resizeStackLocked(ActivityStack stack, Rect bounds, Rect tempTaskBounds,
+ Rect tempTaskInsetBounds, boolean preserveWindows, boolean allowResizeInDockedMode,
+ boolean deferResume) {
+
+ if (stack.inSplitScreenPrimaryWindowingMode()) {
resizeDockedStackLocked(bounds, tempTaskBounds, tempTaskInsetBounds, null, null,
preserveWindows, deferResume);
return;
}
- final ActivityStack stack = getStack(stackId);
- if (stack == null) {
- Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
- return;
- }
- final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenStack();
+ final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenPrimaryStack();
if (!allowResizeInDockedMode
&& !stack.getWindowConfiguration().tasksAreFloating() && splitScreenActive) {
// If the docked stack exists, don't resize non-floating stacks independently of the
@@ -2532,7 +2530,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return;
}
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stack.mStackId);
mWindowManager.deferSurfaceLayout();
try {
if (stack.supportsSplitScreenWindowingMode()) {
@@ -2584,8 +2582,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
/**
* 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.
+ * Can do that once we are no longer using static stack ids.
*/
private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack,
int toDisplayId, boolean onTop) {
@@ -2600,13 +2597,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// 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.
- final ArrayList<ActivityStack> displayStacks = toDisplay.mStacks;
- for (int i = displayStacks.size() - 1; i >= 0; --i) {
- final ActivityStack otherStack = displayStacks.get(i);
+ for (int i = toDisplay.getChildCount() - 1; i >= 0; --i) {
+ final ActivityStack otherStack = toDisplay.getChildAt(i);
if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
continue;
}
- resizeStackLocked(otherStack.mStackId, null, null, null, PRESERVE_WINDOWS,
+ resizeStackLocked(otherStack, null, null, null, PRESERVE_WINDOWS,
true /* allowResizeInDockedMode */, DEFER_RESUME);
}
@@ -2697,8 +2693,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return;
}
- final ActivityStack stack = getDefaultDisplay().getStack(
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+ final ActivityStack stack = getDefaultDisplay().getSplitScreenPrimaryStack();
if (stack == null) {
Slog.w(TAG, "resizeDockedStackLocked: docked stack not found");
return;
@@ -2727,10 +2722,10 @@ 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 ActivityDisplay display = getDefaultDisplay();
final Rect otherTaskRect = new Rect();
- for (int i = stacks.size() - 1; i >= 0; --i) {
- final ActivityStack current = stacks.get(i);
+ for (int i = display.getChildCount() - 1; i >= 0; --i) {
+ final ActivityStack current = display.getChildAt(i);
if (current.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
continue;
}
@@ -2744,7 +2739,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
tempRect /* outStackBounds */,
otherTaskRect /* outTempTaskBounds */, true /* ignoreVisibility */);
- resizeStackLocked(current.mStackId, !tempRect.isEmpty() ? tempRect : null,
+ resizeStackLocked(current, !tempRect.isEmpty() ? tempRect : null,
!otherTaskRect.isEmpty() ? otherTaskRect : tempOtherTaskBounds,
tempOtherTaskInsetBounds, preserveWindows,
true /* allowResizeInDockedMode */, deferResume);
@@ -2762,8 +2757,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
// TODO(multi-display): Pinned stack display should be passed in.
- final PinnedActivityStack stack = getDefaultDisplay().getStack(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+ final PinnedActivityStack stack = getDefaultDisplay().getPinnedStack();
if (stack == null) {
Slog.w(TAG, "resizePinnedStackLocked: pinned stack not found");
return;
@@ -2801,12 +2795,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
}
- private void removeStackInSurfaceTransaction(int stackId) {
- final ActivityStack stack = getStack(stackId);
- if (stack == null) {
- return;
- }
-
+ private void removeStackInSurfaceTransaction(ActivityStack stack) {
final ArrayList<TaskRecord> tasks = stack.getAllTasks();
if (stack.getWindowingMode() == WINDOWING_MODE_PINNED) {
/**
@@ -2836,12 +2825,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
/**
- * Removes the stack associated with the given {@param stackId}. If the {@param stackId} is the
+ * Removes the stack associated with the given {@param stack}. If the {@param stack} is the
* pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but
* instead moved back onto the fullscreen stack.
*/
- void removeStackLocked(int stackId) {
- mWindowManager.inSurfaceTransaction(() -> removeStackInSurfaceTransaction(stackId));
+ void removeStack(ActivityStack stack) {
+ mWindowManager.inSurfaceTransaction(() -> removeStackInSurfaceTransaction(stack));
}
/**
@@ -2892,23 +2881,9 @@ 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) {
- removeTaskFromRecents(tr);
+ mRecentTasks.remove(tr);
}
ComponentName component = tr.getBaseIntent().getComponent();
if (component == null) {
@@ -2979,8 +2954,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
int getNextStackId() {
while (true) {
- if (mNextFreeStackId >= FIRST_DYNAMIC_STACK_ID
- && getStack(mNextFreeStackId) == null) {
+ if (getStack(mNextFreeStackId) == null) {
break;
}
mNextFreeStackId++;
@@ -2989,7 +2963,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
/**
- * Restores a recent task to a stack
+ * Called to restore the state of the task into the stack that it's supposed to go into.
+ *
* @param task The recent task to be restored.
* @param aOptions The activity options to use for restoration.
* @return true if the task has been restored successfully.
@@ -3020,6 +2995,22 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return true;
}
+ @Override
+ public void onRecentTaskAdded(TaskRecord task) {
+ task.touchActiveTime();
+ }
+
+ @Override
+ public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
+ if (wasTrimmed) {
+ // Task was trimmed from the recent tasks list -- remove the active task record as well
+ // since the user won't really be able to go back to it
+ removeTaskByIdLocked(task.taskId, false /* killProcess */,
+ false /* removeFromRecents */);
+ }
+ task.removedFromRecents();
+ }
+
/**
* Move stack with all its existing content to specified display.
* @param stackId Id of stack to move.
@@ -3160,7 +3151,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// Resize the pinned stack to match the current size of the task the activity we are
// going to be moving is currently contained in. We do this to have the right starting
// animation bounds for the pinned stack to the desired bounds the caller wants.
- resizeStackLocked(PINNED_STACK_ID, task.mBounds, null /* tempTaskBounds */,
+ resizeStackLocked(stack, task.mBounds, null /* tempTaskBounds */,
null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
true /* allowResizeInDockedMode */, !DEFER_RESUME);
@@ -3257,9 +3248,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
ActivityRecord affinityMatch = null;
if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r);
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
if (!r.hasCompatibleActivityType(stack)) {
if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) "
+ stack);
@@ -3292,12 +3283,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
ActivityRecord findActivityLocked(Intent intent, ActivityInfo info,
- boolean compareIntentFilters) {
+ boolean compareIntentFilters) {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityRecord ar = stacks.get(stackNdx)
- .findActivityLocked(intent, info, compareIntentFilters);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ final ActivityRecord ar = stack.findActivityLocked(
+ intent, info, compareIntentFilters);
if (ar != null) {
return ar;
}
@@ -3391,9 +3383,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
// Set the sleeping state of the stacks on the display.
- final ArrayList<ActivityStack> stacks = display.mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
if (displayShouldSleep) {
stack.goToSleepIfPossible(false /* shuttingDown */);
} else {
@@ -3455,12 +3446,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
private boolean putStacksToSleepLocked(boolean allowDelay, boolean shuttingDown) {
boolean allSleep = true;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
if (allowDelay) {
- allSleep &= stacks.get(stackNdx).goToSleepIfPossible(shuttingDown);
+ allSleep &= stack.goToSleepIfPossible(shuttingDown);
} else {
- stacks.get(stackNdx).goToSleep();
+ stack.goToSleep();
}
}
}
@@ -3485,11 +3477,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void handleAppCrashLocked(ProcessRecord app) {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- int stackNdx = stacks.size() - 1;
- while (stackNdx >= 0) {
- stacks.get(stackNdx).handleAppCrashLocked(app);
- stackNdx--;
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ stack.handleAppCrashLocked(app);
}
}
}
@@ -3500,7 +3491,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
final ActivityStack stack = task.getStack();
r.mLaunchTaskBehind = false;
- mRecentTasks.addLocked(task);
+ mRecentTasks.add(task);
mService.mTaskChangeNotificationController.notifyTaskStackChanged();
r.setVisibility(false);
@@ -3522,10 +3513,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
try {
// First the front stacks. In case any are not fullscreen and are in front of home.
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- final int topStackNdx = stacks.size() - 1;
- for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
stack.ensureActivitiesVisibleLocked(starting, configChanges, preserveWindows);
}
}
@@ -3536,10 +3526,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- final int topStackNdx = stacks.size() - 1;
- for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
stack.addStartingWindowsForVisibleActivities(taskSwitch);
}
}
@@ -3555,20 +3544,20 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
mTaskLayersChanged = false;
for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); displayNdx++) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
int baseLayer = 0;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- baseLayer += stacks.get(stackNdx).rankTaskLayers(baseLayer);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ baseLayer += stack.rankTaskLayers(baseLayer);
}
}
}
void clearOtherAppTimeTrackers(AppTimeTracker except) {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- final int topStackNdx = stacks.size() - 1;
- for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
stack.clearOtherAppTimeTrackers(except);
}
}
@@ -3576,10 +3565,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void scheduleDestroyAllActivities(ProcessRecord app, String reason) {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- final int numStacks = stacks.size();
- for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
stack.scheduleDestroyActivities(app, reason);
}
}
@@ -3631,10 +3619,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// let's iterate through the tasks and release the oldest one.
final int numDisplays = mActivityDisplays.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ final int stackCount = display.getChildCount();
// Step through all stacks starting from behind, to hit the oldest things first.
- for (int stackNdx = 0; stackNdx < stacks.size(); stackNdx++) {
- final ActivityStack stack = stacks.get(stackNdx);
+ for (int stackNdx = 0; stackNdx < stackCount; stackNdx++) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
// Try to release activities in this stack; if we manage to, we are done.
if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) {
return;
@@ -3646,14 +3635,14 @@ 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.
- final ActivityStack dockedStack = getDefaultDisplay().getSplitScreenStack();
+ final ActivityStack dockedStack = getDefaultDisplay().getSplitScreenPrimaryStack();
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);
+ removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
mUserStackInFront.put(mCurrentUser, focusStackId);
final int restoreStackId = mUserStackInFront.get(userId, mHomeStack.mStackId);
@@ -3661,9 +3650,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
mStartingUsers.add(uss);
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
stack.switchUserLocked(userId);
TaskRecord task = stack.topTask();
if (task != null) {
@@ -3761,9 +3750,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void validateTopActivitiesLocked() {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
final ActivityRecord r = stack.topRunningActivityLocked();
final ActivityState state = r == null ? DESTROYED : r.state;
if (isFocusedStack(stack)) {
@@ -3798,7 +3787,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront);
for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
final ActivityDisplay display = mActivityDisplays.valueAt(i);
- pw.println(prefix + "displayId=" + display.mDisplayId + " mStacks=" + display.mStacks);
+ display.dump(pw, prefix);
}
if (!mWaitingForActivityVisible.isEmpty()) {
pw.print(prefix); pw.println("mWaitingForActivityVisible=");
@@ -3811,8 +3800,7 @@ 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);
+ public void writeToProto(ProtoOutputStream proto) {
super.writeToProto(proto, CONFIGURATION_CONTAINER);
for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
@@ -3828,7 +3816,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
} else {
proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID);
}
- proto.end(token);
}
/**
@@ -3857,9 +3844,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
ArrayList<ActivityRecord> activities = new ArrayList<>();
int numDisplays = mActivityDisplays.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
- ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
if (!dumpVisibleStacksOnly || stack.shouldBeVisible(null)) {
activities.addAll(stack.getDumpActivitiesLocked(name));
}
@@ -3892,11 +3879,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
pw.print("Display #"); pw.print(activityDisplay.mDisplayId);
pw.println(" (activities from top to bottom):");
- ArrayList<ActivityStack> stacks = activityDisplay.mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
pw.println();
- pw.println(" Stack #" + stack.mStackId + ":");
+ pw.println(" Stack #" + stack.mStackId
+ + ": type=" + activityTypeToString(stack.getActivityType())
+ + " mode=" + windowingModeToString(stack.getWindowingMode()));
pw.println(" mFullscreen=" + stack.mFullscreen);
pw.println(" isSleeping=" + stack.shouldSleepActivities());
pw.println(" mBounds=" + stack.mBounds);
@@ -4140,30 +4129,29 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
synchronized (mService) {
- ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
- if (activityDisplay != null) {
- final boolean destroyContentOnRemoval
- = activityDisplay.shouldDestroyContentOnRemove();
- final ArrayList<ActivityStack> stacks = activityDisplay.mStacks;
- while (!stacks.isEmpty()) {
- final ActivityStack stack = stacks.get(0);
- if (destroyContentOnRemoval) {
- moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY,
- false /* onTop */);
- stack.finishAllActivitiesLocked(true /* immediately */);
- } else {
- // 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, true /* onTop */);
- }
+ final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+ if (activityDisplay == null) {
+ return;
+ }
+ final boolean destroyContentOnRemoval
+ = activityDisplay.shouldDestroyContentOnRemove();
+ while (activityDisplay.getChildCount() > 0) {
+ final ActivityStack stack = activityDisplay.getChildAt(0);
+ if (destroyContentOnRemoval) {
+ moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY, false /* onTop */);
+ stack.finishAllActivitiesLocked(true /* immediately */);
+ } else {
+ // 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, true /* onTop */);
}
+ }
- releaseSleepTokens(activityDisplay);
+ releaseSleepTokens(activityDisplay);
- mActivityDisplays.remove(displayId);
- mWindowManager.onDisplayRemoved(displayId);
- }
+ mActivityDisplays.remove(displayId);
+ mWindowManager.onDisplayRemoved(displayId);
}
}
@@ -4235,7 +4223,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
info.userId = stack.mCurrentUser;
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.getIndexOf(stack) : 0;
info.configuration.setTo(stack.getConfiguration());
ArrayList<TaskRecord> tasks = stack.getAllTasks();
@@ -4281,25 +4269,25 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
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(getStackInfo(stacks.get(ndx)));
+ final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ list.add(getStackInfo(stack));
}
}
return list;
}
void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
- int preferredDisplayId, int actualStackId) {
+ int preferredDisplayId, ActivityStack actualStack) {
handleNonResizableTaskIfNeeded(task, preferredWindowingMode, preferredDisplayId,
- actualStackId, false /* forceNonResizable */);
+ actualStack, false /* forceNonResizable */);
}
void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
- int preferredDisplayId, int actualStackId, boolean forceNonResizable) {
+ int preferredDisplayId, ActivityStack actualStack, boolean forceNonResizable) {
final boolean isSecondaryDisplayPreferred =
(preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
- final ActivityStack actualStack = getStack(actualStackId);
final boolean inSplitScreenMode = actualStack != null
&& actualStack.inSplitScreenWindowingMode();
if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
@@ -4315,8 +4303,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// The task landed on an inappropriate display somehow, move it to the default
// display.
// TODO(multi-display): Find proper stack for the task on the default display.
- mService.moveTaskToStack(task.taskId, FULLSCREEN_WORKSPACE_STACK_ID,
- true /* toTop */);
+ mService.setTaskWindowingMode(task.taskId,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, true /* toTop */);
launchOnSecondaryDisplayFailed = true;
} else {
// The task might have landed on a display different from requested.
@@ -4346,10 +4334,9 @@ 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.
- final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenStack();
+ final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenPrimaryStack();
if (dockedStack != null) {
- moveTasksToFullscreenStackLocked(dockedStack,
- actualStackId == dockedStack.getStackId());
+ moveTasksToFullscreenStackLocked(dockedStack, actualStack == dockedStack);
}
} else if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
&& !topActivity.noDisplay) {
@@ -4402,7 +4389,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, ActivityStack prevStack) {
final ActivityStack stack = task.getStack();
if (prevStack == null || prevStack == stack
- || (prevStack.mStackId != PINNED_STACK_ID && stack.mStackId != PINNED_STACK_ID)) {
+ || (!prevStack.inPinnedWindowingMode() && !stack.inPinnedWindowingMode())) {
return;
}
@@ -4561,14 +4548,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
if (display == null) {
return null;
}
- final ArrayList<ActivityStack> stacks = display.mStacks;
- for (int i = stacks.size() - 1; i >= 0; i--) {
- if (stacks.get(i) == stack && i > 0) {
- return stacks.get(i - 1);
+ for (int i = display.getChildCount() - 1; i >= 0; i--) {
+ if (display.getChildAt(i) == stack && i > 0) {
+ return display.getChildAt(i - 1);
}
}
throw new IllegalStateException("Failed to find a stack behind stack=" + stack
- + " in=" + stacks);
+ + " in=" + display);
}
/**
@@ -4681,8 +4667,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
final ActivityDisplay display = mActivityDisplays.valueAt(i);
// Traverse all stacks on a display.
- for (int j = display.mStacks.size() - 1; j >= 0; j--) {
- final ActivityStack stack = display.mStacks.get(j);
+ for (int j = display.getChildCount() - 1; j >= 0; --j) {
+ final ActivityStack stack = display.getChildAt(j);
// Get top activity from a visible stack and add it to the list.
if (stack.shouldBeVisible(null /* starting */)) {
final ActivityRecord top = stack.topActivity();
diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java
index d444c663..6f74d851 100644
--- a/com/android/server/am/ActivityStarter.java
+++ b/com/android/server/am/ActivityStarter.java
@@ -26,15 +26,9 @@ import static android.app.ActivityManager.START_RETURN_INTENT_TO_CALLER;
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.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.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;
@@ -121,7 +115,6 @@ import com.android.internal.app.HeavyWeightSwitcherActivity;
import com.android.internal.app.IVoiceInteractor;
import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch;
import com.android.server.pm.InstantAppResolver;
-import com.android.server.wm.WindowManagerService;
import java.io.PrintWriter;
import java.text.DateFormat;
@@ -140,11 +133,11 @@ class ActivityStarter {
private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
+ private static final int INVALID_LAUNCH_MODE = -1;
private final ActivityManagerService mService;
private final ActivityStackSupervisor mSupervisor;
private final ActivityStartInterceptor mInterceptor;
- private WindowManagerService mWindowManager;
final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>();
@@ -154,9 +147,7 @@ class ActivityStarter {
private int mCallingUid;
private ActivityOptions mOptions;
- private boolean mLaunchSingleTop;
- private boolean mLaunchSingleInstance;
- private boolean mLaunchSingleTask;
+ private int mLaunchMode;
private boolean mLaunchTaskBehind;
private int mLaunchFlags;
@@ -166,6 +157,7 @@ class ActivityStarter {
private boolean mDoResume;
private int mStartFlags;
private ActivityRecord mSourceRecord;
+
// The display to launch the activity onto, barring any strong reason to do otherwise.
private int mPreferredDisplayId;
@@ -195,8 +187,6 @@ class ActivityStarter {
private IVoiceInteractionSession mVoiceSession;
private IVoiceInteractor mVoiceInteractor;
- private boolean mUsingVr2dDisplay;
-
// Last home activity record we attempted to start
private final ActivityRecord[] mLastHomeActivityStartRecord = new ActivityRecord[1];
// The result of the last home activity we attempted to start.
@@ -216,11 +206,9 @@ class ActivityStarter {
mCallingUid = -1;
mOptions = null;
- mLaunchSingleTop = false;
- mLaunchSingleInstance = false;
- mLaunchSingleTask = false;
mLaunchTaskBehind = false;
mLaunchFlags = 0;
+ mLaunchMode = INVALID_LAUNCH_MODE;
mLaunchBounds = null;
@@ -248,16 +236,13 @@ class ActivityStarter {
mVoiceSession = null;
mVoiceInteractor = null;
- mUsingVr2dDisplay = false;
-
mIntentDelivered = false;
}
- ActivityStarter(ActivityManagerService service, ActivityStackSupervisor supervisor) {
+ ActivityStarter(ActivityManagerService service) {
mService = service;
- mSupervisor = supervisor;
+ mSupervisor = mService.mStackSupervisor;
mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
- mUsingVr2dDisplay = false;
}
int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
@@ -610,38 +595,41 @@ class ActivityStarter {
mSupervisor.reportTaskToFrontNoLaunch(mStartActivity);
}
- int startedActivityStackId = INVALID_STACK_ID;
+ ActivityStack startedActivityStack = null;
final ActivityStack currentStack = r.getStack();
if (currentStack != null) {
- startedActivityStackId = currentStack.mStackId;
+ startedActivityStack = currentStack;
} else if (mTargetStack != null) {
- startedActivityStackId = targetStack.mStackId;
+ startedActivityStack = targetStack;
}
- if (startedActivityStackId == DOCKED_STACK_ID) {
- final ActivityStack homeStack = mSupervisor.getDefaultDisplay().getStack(
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ if (startedActivityStack == null) {
+ return;
+ }
+
+ if (startedActivityStack.inSplitScreenPrimaryWindowingMode()) {
+ final ActivityStack homeStack = mSupervisor.mHomeStack;
final boolean homeStackVisible = homeStack != null && homeStack.isVisible();
if (homeStackVisible) {
// We launch an activity while being in home stack, which means either launcher or
// recents into docked stack. We don't want the launched activity to be alone in a
// docked stack, so we want to immediately launch recents too.
if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch.");
- mWindowManager.showRecentApps(true /* fromHome */);
+ mService.mWindowManager.showRecentApps(true /* fromHome */);
}
return;
}
boolean clearedTask = (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK) && (mReuseTask != null);
- if (startedActivityStackId == PINNED_STACK_ID && (result == START_TASK_TO_FRONT
- || result == START_DELIVERED_TO_TOP || clearedTask)) {
+ if (startedActivityStack.inPinnedWindowingMode()
+ && (result == START_TASK_TO_FRONT || result == START_DELIVERED_TO_TOP
+ || clearedTask)) {
// The activity was already running in the pinned stack so it wasn't started, but either
// brought to the front or the new intent was delivered to it since it was already in
// front. Notify anyone interested in this piece of information.
mService.mTaskChangeNotificationController.notifyPinnedActivityRestartAttempt(
clearedTask);
- return;
}
}
@@ -1062,7 +1050,7 @@ class ActivityStarter {
// operations.
if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| isDocumentLaunchesIntoExisting(mLaunchFlags)
- || mLaunchSingleInstance || mLaunchSingleTask) {
+ || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
final TaskRecord task = reusedActivity.getTask();
// In this situation we want to remove all activities from the task up to the one
@@ -1145,7 +1133,7 @@ class ActivityStarter {
&& top.userId == mStartActivity.userId
&& top.app != null && top.app.thread != null
&& ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
- || mLaunchSingleTop || mLaunchSingleTask);
+ || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK));
if (dontStart) {
// For paranoia, make sure we have correctly resumed the top activity.
topStack.mLastPausedActivity = null;
@@ -1164,7 +1152,7 @@ class ActivityStarter {
// 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(), preferredWindowingMode,
- preferredLaunchDisplayId, topStack.mStackId);
+ preferredLaunchDisplayId, topStack);
return START_DELIVERED_TO_TOP;
}
@@ -1227,7 +1215,7 @@ class ActivityStarter {
mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
// Go ahead and tell window manager to execute app transition for this activity
// since the app transition will not be triggered through the resume channel.
- mWindowManager.executeAppTransition();
+ mService.mWindowManager.executeAppTransition();
} else {
// If the target stack was not previously focusable (previous top running activity
// on that stack was not visible) then any prior calls to move the stack to the
@@ -1240,13 +1228,13 @@ class ActivityStarter {
mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,
mOptions);
}
- } else {
- mSupervisor.addRecentActivity(mStartActivity);
+ } else if (mStartActivity != null) {
+ mSupervisor.mRecentTasks.add(mStartActivity.getTask());
}
mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);
mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredWindowingMode,
- preferredLaunchDisplayId, mTargetStack.mStackId);
+ preferredLaunchDisplayId, mTargetStack);
return START_SUCCESS;
}
@@ -1268,13 +1256,13 @@ class ActivityStarter {
mLaunchBounds = getOverrideBounds(r, options, inTask);
- mLaunchSingleTop = r.launchMode == LAUNCH_SINGLE_TOP;
- mLaunchSingleInstance = r.launchMode == LAUNCH_SINGLE_INSTANCE;
- mLaunchSingleTask = r.launchMode == LAUNCH_SINGLE_TASK;
+ mLaunchMode = r.launchMode;
+
mLaunchFlags = adjustLaunchFlagsToDocumentMode(
- r, mLaunchSingleInstance, mLaunchSingleTask, mIntent.getFlags());
+ r, LAUNCH_SINGLE_INSTANCE == mLaunchMode,
+ LAUNCH_SINGLE_TASK == mLaunchMode, mIntent.getFlags());
mLaunchTaskBehind = r.mLaunchTaskBehind
- && !mLaunchSingleTask && !mLaunchSingleInstance
+ && !isLaunchModeOneOf(LAUNCH_SINGLE_TASK, LAUNCH_SINGLE_INSTANCE)
&& (mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
sendNewTaskResultRequestIfNeeded();
@@ -1384,7 +1372,7 @@ class ActivityStarter {
// If this task is empty, then we are adding the first activity -- it
// determines the root, and must be launching as a NEW_TASK.
- if (mLaunchSingleInstance || mLaunchSingleTask) {
+ if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
if (!baseIntent.getComponent().equals(mStartActivity.intent.getComponent())) {
ActivityOptions.abort(mOptions);
throw new IllegalArgumentException("Trying to launch singleInstance/Task "
@@ -1427,7 +1415,7 @@ class ActivityStarter {
// in any task/stack, however it could launch other activities like ResolverActivity,
// and we want those to stay in the original task.
if ((mStartActivity.isResolverActivity() || mStartActivity.noDisplay) && mSourceRecord != null
- && mSourceRecord.isFreeform()) {
+ && mSourceRecord.inFreeformWindowingMode()) {
mAddingToTask = true;
}
}
@@ -1446,7 +1434,7 @@ class ActivityStarter {
// instance... this new activity it is starting must go on its
// own task.
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
- } else if (mLaunchSingleInstance || mLaunchSingleTask) {
+ } else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
// The activity being started is a single instance... it always
// gets launched into its own task.
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
@@ -1497,7 +1485,7 @@ class ActivityStarter {
// launch this as a new task behind the current one.
boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
(mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
- || mLaunchSingleInstance || mLaunchSingleTask;
+ || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);
// If bring to front is requested, and no result is requested and we have not been given
// an explicit task to launch in to, and we can find a task that was started with this
// same component, then instead of launching bring that one to the front.
@@ -1507,7 +1495,7 @@ class ActivityStarter {
final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId());
intentActivity = task != null ? task.getTopActivity() : null;
} else if (putIntoExistingTask) {
- if (mLaunchSingleInstance) {
+ if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {
// There can be one and only one instance of single instance activity in the
// history, and it is always in its own unique task, so we do a special search.
intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
@@ -1516,7 +1504,7 @@ class ActivityStarter {
// For the launch adjacent case we only want to put the activity in an existing
// task if the activity already exists in the history.
intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
- !mLaunchSingleTask);
+ !(LAUNCH_SINGLE_TASK == mLaunchMode));
} else {
// Otherwise find the best task to put the activity in.
intentActivity = mSupervisor.findTaskLocked(mStartActivity, mPreferredDisplayId);
@@ -1545,7 +1533,6 @@ class ActivityStarter {
if (DEBUG_STACK) {
Slog.d(TAG, "getSourceDisplayId :" + displayId);
}
- mUsingVr2dDisplay = true;
return displayId;
}
@@ -1667,7 +1654,7 @@ class ActivityStarter {
}
mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(),
- WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack.mStackId);
+ WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack);
// If the caller has requested that the target task be reset, then do so.
if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
@@ -1719,7 +1706,7 @@ class ActivityStarter {
// mTaskToReturnTo values and we don't want to overwrite them accidentally.
mMovedOtherTask = true;
} else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
- || mLaunchSingleInstance || mLaunchSingleTask) {
+ || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity,
mLaunchFlags);
if (top == null) {
@@ -1748,7 +1735,8 @@ class ActivityStarter {
// so we take that as a request to bring the task to the foreground. If the top
// activity in the task is the root activity, deliver this new intent to it if it
// desires.
- if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop)
+ if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
+ || LAUNCH_SINGLE_TOP == mLaunchMode)
&& intentActivity.realActivity.equals(mStartActivity.realActivity)) {
if (intentActivity.frontOfTask) {
intentActivity.getTask().setIntent(mStartActivity);
@@ -1952,7 +1940,7 @@ class ActivityStarter {
if (top != null && top.realActivity.equals(mStartActivity.realActivity)
&& top.userId == mStartActivity.userId) {
if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
- || mLaunchSingleTop || mLaunchSingleTask) {
+ || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK)) {
mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
mStartActivity.appTimeTracker, "inTaskToFront");
if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
@@ -2001,9 +1989,9 @@ class ActivityStarter {
return;
}
- final int stackId = task.getStackId();
- if (StackId.resizeStackWithLaunchBounds(stackId)) {
- mService.resizeStack(stackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+ final ActivityStack stack = task.getStack();
+ if (stack != null && stack.resizeStackWithLaunchBounds()) {
+ mService.resizeStack(stack.mStackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
} else {
task.updateOverrideConfiguration(bounds);
}
@@ -2115,11 +2103,10 @@ class ActivityStarter {
}
if (stack == null) {
// We first try to put the task in the first dynamic stack on home display.
- final ArrayList<ActivityStack> homeDisplayStacks =
- mSupervisor.getStacksOnDefaultDisplay();
- for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
- stack = homeDisplayStacks.get(stackNdx);
- if (isDynamicStack(stack.mStackId)) {
+ final ActivityDisplay display = mSupervisor.getDefaultDisplay();
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ stack = display.getChildAt(stackNdx);
+ if (!stack.isOnHomeDisplay()) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
"computeStackFocus: Setting focused stack=" + stack);
return stack;
@@ -2138,7 +2125,6 @@ class ActivityStarter {
private boolean canLaunchIntoFocusedStack(ActivityRecord r, boolean newTask) {
final ActivityStack focusedStack = mSupervisor.mFocusedStack;
final boolean canUseFocusedStack;
- final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
if (focusedStack.isActivityTypeAssistant()) {
canUseFocusedStack = r.isActivityTypeAssistant();
} else {
@@ -2161,7 +2147,7 @@ class ActivityStarter {
default:
// Dynamic stacks behave similarly to the fullscreen stack and can contain any
// resizeable task.
- canUseFocusedStack = isDynamicStack(focusedStackId)
+ canUseFocusedStack = !focusedStack.isOnHomeDisplay()
&& r.canBeLaunchedOnDisplay(focusedStack.mDisplayId);
}
}
@@ -2177,7 +2163,8 @@ class ActivityStarter {
return mReuseTask.getStack();
}
- final int vrDisplayId = mUsingVr2dDisplay ? mPreferredDisplayId : INVALID_DISPLAY;
+ final int vrDisplayId = mPreferredDisplayId == mService.mVr2dDisplayId
+ ? mPreferredDisplayId : INVALID_DISPLAY;
final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
vrDisplayId);
@@ -2205,7 +2192,7 @@ class ActivityStarter {
return mSupervisor.mFocusedStack;
}
- if (parentStack != null && parentStack.isDockedStack()) {
+ if (parentStack != null && parentStack.inSplitScreenPrimaryWindowingMode()) {
// If parent was in docked stack, the natural place to launch another activity
// will be fullscreen, so it can appear alongside the docked window.
final int activityType = mSupervisor.resolveActivityType(r, mOptions, task);
@@ -2215,8 +2202,8 @@ class ActivityStarter {
// 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.getDefaultDisplay().getStack(
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+ final ActivityStack dockedStack =
+ mSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
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;
@@ -2236,8 +2223,8 @@ class ActivityStarter {
return newBounds;
}
- void setWindowManager(WindowManagerService wm) {
- mWindowManager = wm;
+ private boolean isLaunchModeOneOf(int mode1, int mode2) {
+ return mode1 == mLaunchMode || mode2 == mLaunchMode;
}
static boolean isDocumentLaunchesIntoExisting(int flags) {
@@ -2318,11 +2305,11 @@ class ActivityStarter {
}
pw.print(prefix);
pw.print("mLaunchSingleTop=");
- pw.print(mLaunchSingleTop);
+ pw.print(LAUNCH_SINGLE_TOP == mLaunchMode);
pw.print(" mLaunchSingleInstance=");
- pw.print(mLaunchSingleInstance);
+ pw.print(LAUNCH_SINGLE_INSTANCE == mLaunchMode);
pw.print(" mLaunchSingleTask=");
- pw.println(mLaunchSingleTask);
+ pw.println(LAUNCH_SINGLE_TASK == mLaunchMode);
pw.print(prefix);
pw.print("mLaunchFlags=0x");
pw.print(Integer.toHexString(mLaunchFlags));
diff --git a/com/android/server/am/AppTaskImpl.java b/com/android/server/am/AppTaskImpl.java
new file mode 100644
index 00000000..a4e2e70e
--- /dev/null
+++ b/com/android/server/am/AppTaskImpl.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 com.android.server.am;
+
+import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
+
+import android.app.ActivityManager;
+import android.app.IAppTask;
+import android.app.IApplicationThread;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+
+/**
+ * An implementation of IAppTask, that allows an app to manage its own tasks via
+ * {@link android.app.ActivityManager.AppTask}. We keep track of the callingUid to ensure that
+ * only the process that calls getAppTasks() can call the AppTask methods.
+ */
+class AppTaskImpl extends IAppTask.Stub {
+ private ActivityManagerService mService;
+
+ private int mTaskId;
+ private int mCallingUid;
+
+ public AppTaskImpl(ActivityManagerService service, int taskId, int callingUid) {
+ mService = service;
+ mTaskId = taskId;
+ mCallingUid = callingUid;
+ }
+
+ private void checkCaller() {
+ if (mCallingUid != Binder.getCallingUid()) {
+ throw new SecurityException("Caller " + mCallingUid
+ + " does not match caller of getAppTasks(): " + Binder.getCallingUid());
+ }
+ }
+
+ @Override
+ public void finishAndRemoveTask() {
+ checkCaller();
+
+ synchronized (mService) {
+ long origId = Binder.clearCallingIdentity();
+ try {
+ // We remove the task from recents to preserve backwards
+ if (!mService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
+ REMOVE_FROM_RECENTS)) {
+ throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
+ @Override
+ public ActivityManager.RecentTaskInfo getTaskInfo() {
+ checkCaller();
+
+ synchronized (mService) {
+ long origId = Binder.clearCallingIdentity();
+ try {
+ TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId);
+ if (tr == null) {
+ throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+ }
+ return RecentTasks.createRecentTaskInfo(tr);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
+ @Override
+ public void moveToFront() {
+ checkCaller();
+ // Will bring task to front if it already has a root activity.
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ mService.mStackSupervisor.startActivityFromRecentsInner(mTaskId, null);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
+ public int startActivity(IBinder whoThread, String callingPackage,
+ Intent intent, String resolvedType, Bundle bOptions) {
+ checkCaller();
+
+ int callingUser = UserHandle.getCallingUserId();
+ TaskRecord tr;
+ IApplicationThread appThread;
+ synchronized (mService) {
+ tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId);
+ if (tr == null) {
+ throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+ }
+ appThread = IApplicationThread.Stub.asInterface(whoThread);
+ if (appThread == null) {
+ throw new IllegalArgumentException("Bad app thread " + appThread);
+ }
+ }
+ return mService.mActivityStarter.startActivityMayWait(appThread, -1, callingPackage,
+ intent, resolvedType, null, null, null, null, 0, 0, null, null,
+ null, bOptions, false, callingUser, tr, "AppTaskImpl");
+ }
+
+ @Override
+ public void setExcludeFromRecents(boolean exclude) {
+ checkCaller();
+
+ synchronized (mService) {
+ long origId = Binder.clearCallingIdentity();
+ try {
+ TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId);
+ if (tr == null) {
+ throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+ }
+ Intent intent = tr.getBaseIntent();
+ if (exclude) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ } else {
+ intent.setFlags(intent.getFlags()
+ & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/com/android/server/am/BatteryStatsService.java b/com/android/server/am/BatteryStatsService.java
index 7c9cd00e..68ed9aec 100644
--- a/com/android/server/am/BatteryStatsService.java
+++ b/com/android/server/am/BatteryStatsService.java
@@ -297,26 +297,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
void noteProcessStart(String name, int uid) {
synchronized (mStats) {
mStats.noteProcessStartLocked(name, uid);
-
- // TODO: remove this once we figure out properly where and how
- // PROCESS_EVENT = 1112
- // KEY_STATE = 1
- // KEY_PACKAGE_NAME: 1002
- // KEY_UID: 2
- StatsLog.writeArray(1112, 1, 1, 1002, name, 2, uid);
}
}
void noteProcessCrash(String name, int uid) {
synchronized (mStats) {
mStats.noteProcessCrashLocked(name, uid);
-
- // TODO: remove this once we figure out properly where and how
- // PROCESS_EVENT = 1112
- // KEY_STATE = 1
- // KEY_PACKAGE_NAME: 1002
- // KEY_UID: 2
- StatsLog.writeArray(1112, 1, 2, 1002, name, 2, uid);
}
}
@@ -334,6 +320,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
void noteUidProcessState(int uid, int state) {
synchronized (mStats) {
+ // TODO: remove this once we figure out properly where and how
+ StatsLog.write(StatsLog.PROCESS_STATE_CHANGED, uid, state);
+
mStats.noteUidProcessStateLocked(uid, state);
}
}
@@ -548,12 +537,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
enforceCallingPermission();
if (DBG) Slog.d(TAG, "begin noteScreenState");
synchronized (mStats) {
- mStats.noteScreenStateLocked(state);
// TODO: remove this once we figure out properly where and how
- // SCREEN_EVENT = 2
- // KEY_STATE: 1
- // State value: state. We can change this to our own def later.
- StatsLog.writeArray(2, 1, state);
+ StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, state);
+
+ mStats.noteScreenStateLocked(state);
}
if (DBG) Slog.d(TAG, "end noteScreenState");
}
diff --git a/com/android/server/am/BroadcastFilter.java b/com/android/server/am/BroadcastFilter.java
index f96b06fa..7ff227f5 100644
--- a/com/android/server/am/BroadcastFilter.java
+++ b/com/android/server/am/BroadcastFilter.java
@@ -19,6 +19,9 @@ package com.android.server.am;
import android.content.IntentFilter;
import android.util.PrintWriterPrinter;
import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.BroadcastFilterProto;
import java.io.PrintWriter;
@@ -44,27 +47,38 @@ final class BroadcastFilter extends IntentFilter {
instantApp = _instantApp;
visibleToInstantApp = _visibleToInstantApp;
}
-
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ super.writeToProto(proto, BroadcastFilterProto.INTENT_FILTER);
+ if (requiredPermission != null) {
+ proto.write(BroadcastFilterProto.REQUIRED_PERMISSION, requiredPermission);
+ }
+ proto.write(BroadcastFilterProto.HEX_HASH, Integer.toHexString(System.identityHashCode(this)));
+ proto.write(BroadcastFilterProto.OWNING_USER_ID, owningUserId);
+ proto.end(token);
+ }
+
public void dump(PrintWriter pw, String prefix) {
dumpInReceiverList(pw, new PrintWriterPrinter(pw), prefix);
receiverList.dumpLocal(pw, prefix);
}
-
+
public void dumpBrief(PrintWriter pw, String prefix) {
dumpBroadcastFilterState(pw, prefix);
}
-
+
public void dumpInReceiverList(PrintWriter pw, Printer pr, String prefix) {
super.dump(pr, prefix);
dumpBroadcastFilterState(pw, prefix);
}
-
+
void dumpBroadcastFilterState(PrintWriter pw, String prefix) {
if (requiredPermission != null) {
pw.print(prefix); pw.print("requiredPermission="); pw.println(requiredPermission);
}
}
-
+
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("BroadcastFilter{");
diff --git a/com/android/server/am/BroadcastQueue.java b/com/android/server/am/BroadcastQueue.java
index d8354549..c62cc38b 100644
--- a/com/android/server/am/BroadcastQueue.java
+++ b/com/android/server/am/BroadcastQueue.java
@@ -51,9 +51,12 @@ import android.os.UserHandle;
import android.util.EventLog;
import android.util.Slog;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
import static com.android.server.am.ActivityManagerDebugConfig.*;
+import com.android.server.am.proto.BroadcastQueueProto;
+
/**
* BROADCASTS
*
@@ -1585,6 +1588,55 @@ public final class BroadcastQueue {
&& (mPendingBroadcast == null);
}
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
+ int N;
+ N = mParallelBroadcasts.size();
+ for (int i = N - 1; i >= 0; i--) {
+ mParallelBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
+ }
+ N = mOrderedBroadcasts.size();
+ for (int i = N - 1; i >= 0; i--) {
+ mOrderedBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
+ }
+ if (mPendingBroadcast != null) {
+ mPendingBroadcast.writeToProto(proto, BroadcastQueueProto.PENDING_BROADCAST);
+ }
+
+ int lastIndex = mHistoryNext;
+ int ringIndex = lastIndex;
+ do {
+ // increasing index = more recent entry, and we want to print the most
+ // recent first and work backwards, so we roll through the ring backwards.
+ ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
+ BroadcastRecord r = mBroadcastHistory[ringIndex];
+ if (r != null) {
+ r.writeToProto(proto, BroadcastQueueProto.HISTORICAL_BROADCASTS);
+ }
+ } while (ringIndex != lastIndex);
+
+ lastIndex = ringIndex = mSummaryHistoryNext;
+ do {
+ ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
+ Intent intent = mBroadcastSummaryHistory[ringIndex];
+ if (intent == null) {
+ continue;
+ }
+ long summaryToken = proto.start(BroadcastQueueProto.HISTORICAL_BROADCASTS_SUMMARY);
+ intent.writeToProto(proto, BroadcastQueueProto.BroadcastSummary.INTENT,
+ false, true, true, false);
+ proto.write(BroadcastQueueProto.BroadcastSummary.ENQUEUE_CLOCK_TIME_MS,
+ mSummaryHistoryEnqueueTime[ringIndex]);
+ proto.write(BroadcastQueueProto.BroadcastSummary.DISPATCH_CLOCK_TIME_MS,
+ mSummaryHistoryDispatchTime[ringIndex]);
+ proto.write(BroadcastQueueProto.BroadcastSummary.FINISH_CLOCK_TIME_MS,
+ mSummaryHistoryFinishTime[ringIndex]);
+ proto.end(summaryToken);
+ } while (ringIndex != lastIndex);
+ proto.end(token);
+ }
+
final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
diff --git a/com/android/server/am/BroadcastRecord.java b/com/android/server/am/BroadcastRecord.java
index 6bc0744f..5b3b2a8e 100644
--- a/com/android/server/am/BroadcastRecord.java
+++ b/com/android/server/am/BroadcastRecord.java
@@ -30,6 +30,9 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.util.PrintWriterPrinter;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.BroadcastRecordProto;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
@@ -331,9 +334,17 @@ final class BroadcastRecord extends Binder {
return didSomething;
}
+ @Override
public String toString() {
return "BroadcastRecord{"
+ Integer.toHexString(System.identityHashCode(this))
+ " u" + userId + " " + intent.getAction() + "}";
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(BroadcastRecordProto.USER_ID, userId);
+ proto.write(BroadcastRecordProto.INTENT_ACTION, intent.getAction());
+ proto.end(token);
+ }
}
diff --git a/com/android/server/am/KeyguardController.java b/com/android/server/am/KeyguardController.java
index 5c48f90d..c3fed171 100644
--- a/com/android/server/am/KeyguardController.java
+++ b/com/android/server/am/KeyguardController.java
@@ -16,7 +16,6 @@
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;
@@ -47,7 +46,6 @@ import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.server.wm.WindowManagerService;
import java.io.PrintWriter;
-import java.util.ArrayList;
/**
* Controls Keyguard occluding, dismissing and transitions depending on what kind of activities are
@@ -238,9 +236,9 @@ class KeyguardController {
final ActivityRecord lastDismissingKeyguardActivity = mDismissingKeyguardActivity;
mOccluded = false;
mDismissingKeyguardActivity = null;
- final ArrayList<ActivityStack> stacks = mStackSupervisor.getStacksOnDefaultDisplay();
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityDisplay display = mStackSupervisor.getDefaultDisplay();
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
// Only the focused stack top activity may control occluded state
if (mStackSupervisor.isFocusedStack(stack)) {
@@ -342,7 +340,7 @@ 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.
- final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getSplitScreenStack();
+ final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
if (stack == null) {
return;
}
diff --git a/com/android/server/am/LaunchingTaskPositioner.java b/com/android/server/am/LaunchingTaskPositioner.java
index d6523418..0dc73e98 100644
--- a/com/android/server/am/LaunchingTaskPositioner.java
+++ b/com/android/server/am/LaunchingTaskPositioner.java
@@ -24,8 +24,8 @@ import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Slog;
-import android.view.Display;
import android.view.Gravity;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
@@ -64,43 +64,11 @@ class LaunchingTaskPositioner {
private static final int SHIFT_POLICY_HORIZONTAL_RIGHT = 2;
private static final int SHIFT_POLICY_HORIZONTAL_LEFT = 3;
- private boolean mDefaultStartBoundsConfigurationSet = false;
private final Rect mAvailableRect = new Rect();
private final Rect mTmpProposal = new Rect();
private final Rect mTmpOriginal = new Rect();
- private int mDefaultFreeformStartX;
- private int mDefaultFreeformStartY;
- private int mDefaultFreeformWidth;
- private int mDefaultFreeformHeight;
- private int mDefaultFreeformStepHorizontal;
- private int mDefaultFreeformStepVertical;
- private int mDisplayWidth;
- private int mDisplayHeight;
-
- void setDisplay(Display display) {
- Point size = new Point();
- display.getSize(size);
- mDisplayWidth = size.x;
- mDisplayHeight = size.y;
- }
-
- void configure(Rect stackBounds) {
- if (stackBounds == null) {
- mAvailableRect.set(0, 0, mDisplayWidth, mDisplayHeight);
- } else {
- mAvailableRect.set(stackBounds);
- }
- int width = mAvailableRect.width();
- int height = mAvailableRect.height();
- mDefaultFreeformStartX = mAvailableRect.left + width / MARGIN_SIZE_DENOMINATOR;
- mDefaultFreeformStartY = mAvailableRect.top + height / MARGIN_SIZE_DENOMINATOR;
- mDefaultFreeformWidth = width / WINDOW_SIZE_DENOMINATOR;
- mDefaultFreeformHeight = height / WINDOW_SIZE_DENOMINATOR;
- mDefaultFreeformStepHorizontal = Math.max(width / STEP_DENOMINATOR, MINIMAL_STEP);
- mDefaultFreeformStepVertical = Math.max(height / STEP_DENOMINATOR, MINIMAL_STEP);
- mDefaultStartBoundsConfigurationSet = true;
- }
+ private final Point mDisplaySize = new Point();
/**
* Tries to set task's bound in a way that it won't collide with any other task. By colliding
@@ -114,104 +82,154 @@ class LaunchingTaskPositioner {
*/
void updateDefaultBounds(TaskRecord task, ArrayList<TaskRecord> tasks,
@Nullable ActivityInfo.WindowLayout windowLayout) {
- if (!mDefaultStartBoundsConfigurationSet) {
- return;
- }
+ updateAvailableRect(task, mAvailableRect);
+
if (windowLayout == null) {
- positionCenter(task, tasks, mDefaultFreeformWidth, mDefaultFreeformHeight);
+ positionCenter(task, tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
+ getFreeformHeight(mAvailableRect));
return;
}
- int width = getFinalWidth(windowLayout);
- int height = getFinalHeight(windowLayout);
+ int width = getFinalWidth(windowLayout, mAvailableRect);
+ int height = getFinalHeight(windowLayout, mAvailableRect);
int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
if (verticalGravity == Gravity.TOP) {
if (horizontalGravity == Gravity.RIGHT) {
- positionTopRight(task, tasks, width, height);
+ positionTopRight(task, tasks, mAvailableRect, width, height);
} else {
- positionTopLeft(task, tasks, width, height);
+ positionTopLeft(task, tasks, mAvailableRect, width, height);
}
} else if (verticalGravity == Gravity.BOTTOM) {
if (horizontalGravity == Gravity.RIGHT) {
- positionBottomRight(task, tasks, width, height);
+ positionBottomRight(task, tasks, mAvailableRect, width, height);
} else {
- positionBottomLeft(task, tasks, width, height);
+ positionBottomLeft(task, tasks, mAvailableRect, width, height);
}
} else {
// Some fancy gravity setting that we don't support yet. We just put the activity in the
// center.
Slog.w(TAG, "Received unsupported gravity: " + windowLayout.gravity
+ ", positioning in the center instead.");
- positionCenter(task, tasks, width, height);
+ positionCenter(task, tasks, mAvailableRect, width, height);
}
}
- private int getFinalWidth(ActivityInfo.WindowLayout windowLayout) {
- int width = mDefaultFreeformWidth;
+ private void updateAvailableRect(TaskRecord task, Rect availableRect) {
+ final Rect stackBounds = task.getStack().mBounds;
+
+ if (stackBounds != null) {
+ availableRect.set(stackBounds);
+ } else {
+ task.getStack().getDisplay().mDisplay.getSize(mDisplaySize);
+ availableRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
+ }
+ }
+
+ @VisibleForTesting
+ static int getFreeformStartLeft(Rect bounds) {
+ return bounds.left + bounds.width() / MARGIN_SIZE_DENOMINATOR;
+ }
+
+ @VisibleForTesting
+ static int getFreeformStartTop(Rect bounds) {
+ return bounds.top + bounds.height() / MARGIN_SIZE_DENOMINATOR;
+ }
+
+ @VisibleForTesting
+ static int getFreeformWidth(Rect bounds) {
+ return bounds.width() / WINDOW_SIZE_DENOMINATOR;
+ }
+
+ @VisibleForTesting
+ static int getFreeformHeight(Rect bounds) {
+ return bounds.height() / WINDOW_SIZE_DENOMINATOR;
+ }
+
+ @VisibleForTesting
+ static int getHorizontalStep(Rect bounds) {
+ return Math.max(bounds.width() / STEP_DENOMINATOR, MINIMAL_STEP);
+ }
+
+ @VisibleForTesting
+ static int getVerticalStep(Rect bounds) {
+ return Math.max(bounds.height() / STEP_DENOMINATOR, MINIMAL_STEP);
+ }
+
+
+
+ private int getFinalWidth(ActivityInfo.WindowLayout windowLayout, Rect availableRect) {
+ int width = getFreeformWidth(availableRect);
if (windowLayout.width > 0) {
width = windowLayout.width;
}
if (windowLayout.widthFraction > 0) {
- width = (int) (mAvailableRect.width() * windowLayout.widthFraction);
+ width = (int) (availableRect.width() * windowLayout.widthFraction);
}
return width;
}
- private int getFinalHeight(ActivityInfo.WindowLayout windowLayout) {
- int height = mDefaultFreeformHeight;
+ private int getFinalHeight(ActivityInfo.WindowLayout windowLayout, Rect availableRect) {
+ int height = getFreeformHeight(availableRect);
if (windowLayout.height > 0) {
height = windowLayout.height;
}
if (windowLayout.heightFraction > 0) {
- height = (int) (mAvailableRect.height() * windowLayout.heightFraction);
+ height = (int) (availableRect.height() * windowLayout.heightFraction);
}
return height;
}
- private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
- int height) {
- mTmpProposal.set(mAvailableRect.left, mAvailableRect.bottom - height,
- mAvailableRect.left + width, mAvailableRect.bottom);
- position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT);
+ private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks,
+ Rect availableRect, int width, int height) {
+ mTmpProposal.set(availableRect.left, availableRect.bottom - height,
+ availableRect.left + width, availableRect.bottom);
+ position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
+ SHIFT_POLICY_HORIZONTAL_RIGHT);
}
- private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
- int height) {
- mTmpProposal.set(mAvailableRect.right - width, mAvailableRect.bottom - height,
- mAvailableRect.right, mAvailableRect.bottom);
- position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT);
+ private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks,
+ Rect availableRect, int width, int height) {
+ mTmpProposal.set(availableRect.right - width, availableRect.bottom - height,
+ availableRect.right, availableRect.bottom);
+ position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
+ SHIFT_POLICY_HORIZONTAL_LEFT);
}
- private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
- int height) {
- mTmpProposal.set(mAvailableRect.left, mAvailableRect.top,
- mAvailableRect.left + width, mAvailableRect.top + height);
- position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT);
+ private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks,
+ Rect availableRect, int width, int height) {
+ mTmpProposal.set(availableRect.left, availableRect.top,
+ availableRect.left + width, availableRect.top + height);
+ position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
+ SHIFT_POLICY_HORIZONTAL_RIGHT);
}
- private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
- int height) {
- mTmpProposal.set(mAvailableRect.right - width, mAvailableRect.top,
- mAvailableRect.right, mAvailableRect.top + height);
- position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT);
+ private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks,
+ Rect availableRect, int width, int height) {
+ mTmpProposal.set(availableRect.right - width, availableRect.top,
+ availableRect.right, availableRect.top + height);
+ position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
+ SHIFT_POLICY_HORIZONTAL_LEFT);
}
- private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
- int height) {
- mTmpProposal.set(mDefaultFreeformStartX, mDefaultFreeformStartY,
- mDefaultFreeformStartX + width, mDefaultFreeformStartY + height);
- position(task, tasks, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN);
+ private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks,
+ Rect availableRect, int width, int height) {
+ final int defaultFreeformLeft = getFreeformStartLeft(availableRect);
+ final int defaultFreeformTop = getFreeformStartTop(availableRect);
+ mTmpProposal.set(defaultFreeformLeft, defaultFreeformTop,
+ defaultFreeformLeft + width, defaultFreeformTop + height);
+ position(task, tasks, availableRect, mTmpProposal, ALLOW_RESTART,
+ SHIFT_POLICY_DIAGONAL_DOWN);
}
- private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect proposal,
- boolean allowRestart, int shiftPolicy) {
+ private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect availableRect,
+ Rect proposal, boolean allowRestart, int shiftPolicy) {
mTmpOriginal.set(proposal);
boolean restarted = false;
while (boundsConflict(proposal, tasks)) {
// Unfortunately there is already a task at that spot, so we need to look for some
// other place.
- shiftStartingPoint(proposal, shiftPolicy);
- if (shiftedToFar(proposal, shiftPolicy)) {
+ shiftStartingPoint(proposal, availableRect, shiftPolicy);
+ if (shiftedTooFar(proposal, availableRect, shiftPolicy)) {
// We don't want the task to go outside of the stack, because it won't look
// nice. Depending on the starting point we either restart, or immediately give up.
if (!allowRestart) {
@@ -220,13 +238,13 @@ class LaunchingTaskPositioner {
}
// We must have started not from the top. Let's restart from there because there
// might be some space there.
- proposal.set(mAvailableRect.left, mAvailableRect.top,
- mAvailableRect.left + proposal.width(),
- mAvailableRect.top + proposal.height());
+ proposal.set(availableRect.left, availableRect.top,
+ availableRect.left + proposal.width(),
+ availableRect.top + proposal.height());
restarted = true;
}
- if (restarted && (proposal.left > mDefaultFreeformStartX
- || proposal.top > mDefaultFreeformStartY)) {
+ if (restarted && (proposal.left > getFreeformStartLeft(availableRect)
+ || proposal.top > getFreeformStartTop(availableRect))) {
// If we restarted and crossed the initial position, let's not struggle anymore.
// The user already must have ton of tasks visible, we can just smack the new
// one in the center.
@@ -237,27 +255,30 @@ class LaunchingTaskPositioner {
task.updateOverrideConfiguration(proposal);
}
- private boolean shiftedToFar(Rect start, int shiftPolicy) {
+ private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) {
switch (shiftPolicy) {
case SHIFT_POLICY_HORIZONTAL_LEFT:
- return start.left < mAvailableRect.left;
+ return start.left < availableRect.left;
case SHIFT_POLICY_HORIZONTAL_RIGHT:
- return start.right > mAvailableRect.right;
+ return start.right > availableRect.right;
default: // SHIFT_POLICY_DIAGONAL_DOWN
- return start.right > mAvailableRect.right || start.bottom > mAvailableRect.bottom;
+ return start.right > availableRect.right || start.bottom > availableRect.bottom;
}
}
- private void shiftStartingPoint(Rect posposal, int shiftPolicy) {
+ private void shiftStartingPoint(Rect posposal, Rect availableRect, int shiftPolicy) {
+ final int defaultFreeformStepHorizontal = getHorizontalStep(availableRect);
+ final int defaultFreeformStepVertical = getVerticalStep(availableRect);
+
switch (shiftPolicy) {
case SHIFT_POLICY_HORIZONTAL_LEFT:
- posposal.offset(-mDefaultFreeformStepHorizontal, 0);
+ posposal.offset(-defaultFreeformStepHorizontal, 0);
break;
case SHIFT_POLICY_HORIZONTAL_RIGHT:
- posposal.offset(mDefaultFreeformStepHorizontal, 0);
+ posposal.offset(defaultFreeformStepHorizontal, 0);
break;
default: // SHIFT_POLICY_DIAGONAL_DOWN:
- posposal.offset(mDefaultFreeformStepHorizontal, mDefaultFreeformStepVertical);
+ posposal.offset(defaultFreeformStepHorizontal, defaultFreeformStepVertical);
break;
}
}
@@ -296,8 +317,4 @@ class LaunchingTaskPositioner {
return Math.abs(first.right - second.right) < BOUNDS_CONFLICT_MIN_DISTANCE
&& Math.abs(first.bottom - second.bottom) < BOUNDS_CONFLICT_MIN_DISTANCE;
}
-
- void reset() {
- mDefaultStartBoundsConfigurationSet = false;
- }
}
diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java
index 72b5de88..940f9051 100644
--- a/com/android/server/am/LockTaskController.java
+++ b/com/android/server/am/LockTaskController.java
@@ -19,7 +19,6 @@ package com.android.server.am;
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.StatusBarManager.DISABLE_BACK;
import static android.app.StatusBarManager.DISABLE_HOME;
import static android.app.StatusBarManager.DISABLE_MASK;
@@ -59,7 +58,6 @@ 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;
@@ -433,7 +431,7 @@ public class LockTaskController {
mWindowManager.executeAppTransition();
} else if (lockTaskModeState != LOCK_TASK_MODE_NONE) {
mSupervisor.handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
- DEFAULT_DISPLAY, task.getStackId(), true /* forceNonResizable */);
+ DEFAULT_DISPLAY, task.getStack(), true /* forceNonResizable */);
}
}
@@ -494,11 +492,7 @@ public class LockTaskController {
}
for (int displayNdx = mSupervisor.getChildCount() - 1; displayNdx >= 0; --displayNdx) {
- ArrayList<ActivityStack> stacks = mSupervisor.getChildAt(displayNdx).mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
- stack.onLockTaskPackagesUpdatedLocked();
- }
+ mSupervisor.getChildAt(displayNdx).onLockTaskPackagesUpdated();
}
final ActivityRecord r = mSupervisor.topRunningActivityLocked();
diff --git a/com/android/server/am/ProcessRecord.java b/com/android/server/am/ProcessRecord.java
index 0e318d9c..e8477231 100644
--- a/com/android/server/am/ProcessRecord.java
+++ b/com/android/server/am/ProcessRecord.java
@@ -27,6 +27,7 @@ import android.util.Slog;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.app.procstats.ProcessState;
import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.am.proto.ProcessRecordProto;
import android.app.ActivityManager;
import android.app.Dialog;
@@ -44,6 +45,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -621,6 +623,22 @@ final class ProcessRecord {
}
}
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(ProcessRecordProto.PID, pid);
+ proto.write(ProcessRecordProto.PROCESS_NAME, processName);
+ if (info.uid < Process.FIRST_APPLICATION_UID) {
+ proto.write(ProcessRecordProto.UID, uid);
+ } else {
+ proto.write(ProcessRecordProto.USER_ID, userId);
+ proto.write(ProcessRecordProto.APP_ID, UserHandle.getAppId(info.uid));
+ if (uid != info.uid) {
+ proto.write(ProcessRecordProto.ISOLATED_APP_ID, UserHandle.getAppId(uid));
+ }
+ }
+ proto.end(token);
+ }
+
public String toShortString() {
if (shortStringName != null) {
return shortStringName;
diff --git a/com/android/server/am/ReceiverList.java b/com/android/server/am/ReceiverList.java
index 6ade7361..a9890631 100644
--- a/com/android/server/am/ReceiverList.java
+++ b/com/android/server/am/ReceiverList.java
@@ -21,6 +21,8 @@ import android.os.Binder;
import android.os.IBinder;
import android.util.PrintWriterPrinter;
import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+import com.android.server.am.proto.ReceiverListProto;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -41,7 +43,7 @@ final class ReceiverList extends ArrayList<BroadcastFilter>
boolean linkedToDeath = false;
String stringName;
-
+
ReceiverList(ActivityManagerService _owner, ProcessRecord _app,
int _pid, int _uid, int _userId, IIntentReceiver _receiver) {
owner = _owner;
@@ -59,12 +61,31 @@ final class ReceiverList extends ArrayList<BroadcastFilter>
public int hashCode() {
return System.identityHashCode(this);
}
-
+
public void binderDied() {
linkedToDeath = false;
owner.unregisterReceiver(receiver);
}
-
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ app.writeToProto(proto, ReceiverListProto.APP);
+ proto.write(ReceiverListProto.PID, pid);
+ proto.write(ReceiverListProto.UID, uid);
+ proto.write(ReceiverListProto.USER, userId);
+ if (curBroadcast != null) {
+ curBroadcast.writeToProto(proto, ReceiverListProto.CURRENT);
+ }
+ proto.write(ReceiverListProto.LINKED_TO_DEATH, linkedToDeath);
+ final int N = size();
+ for (int i=0; i<N; i++) {
+ BroadcastFilter bf = get(i);
+ bf.writeToProto(proto, ReceiverListProto.FILTERS);
+ }
+ proto.write(ReceiverListProto.HEX_HASH, Integer.toHexString(System.identityHashCode(this)));
+ proto.end(token);
+ }
+
void dumpLocal(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("app="); pw.print(app != null ? app.toShortString() : null);
pw.print(" pid="); pw.print(pid); pw.print(" uid="); pw.print(uid);
@@ -74,7 +95,7 @@ final class ReceiverList extends ArrayList<BroadcastFilter>
pw.print(" linkedToDeath="); pw.println(linkedToDeath);
}
}
-
+
void dump(PrintWriter pw, String prefix) {
Printer pr = new PrintWriterPrinter(pw);
dumpLocal(pw, prefix);
@@ -89,7 +110,7 @@ final class ReceiverList extends ArrayList<BroadcastFilter>
bf.dumpInReceiverList(pw, pr, p2);
}
}
-
+
public String toString() {
if (stringName != null) {
return stringName;
diff --git a/com/android/server/am/RecentTasks.java b/com/android/server/am/RecentTasks.java
index 365c5b1d..78274bdc 100644
--- a/com/android/server/am/RecentTasks.java
+++ b/com/android/server/am/RecentTasks.java
@@ -16,15 +16,25 @@
package com.android.server.am;
+import static android.app.ActivityManager.FLAG_AND_UNLOCKED;
+import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
+import static android.app.ActivityManager.RECENT_WITH_EXCLUDED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+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_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
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.REMOVE_FROM_RECENTS;
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
import com.google.android.collect.Sets;
@@ -37,43 +47,87 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Bundle;
import android.os.Environment;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.util.MutableBoolean;
+import android.util.MutableInt;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.ActivityStack.ActivityState;
+
import java.io.File;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
- * Class for managing the recent tasks list.
+ * Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the
+ * least recent.
*/
-class RecentTasks extends ArrayList<TaskRecord> {
+class RecentTasks {
private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM;
private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
private static final String TAG_TASKS = TAG + POSTFIX_TASKS;
+ private static final boolean TRIMMED = true;
- // Maximum number recent bitmaps to keep in memory.
- private static final int MAX_RECENT_BITMAPS = 3;
private static final int DEFAULT_INITIAL_CAPACITY = 5;
// Whether or not to move all affiliated tasks to the front when one of the tasks is launched
private static final boolean MOVE_AFFILIATED_TASKS_TO_FRONT = false;
+ // Comparator to sort by taskId
+ private static final Comparator<TaskRecord> TASK_ID_COMPARATOR =
+ (lhs, rhs) -> rhs.taskId - lhs.taskId;
+
+ // Placeholder variables to keep track of activities/apps that are no longer avialble while
+ // iterating through the recents list
+ private static final ActivityInfo NO_ACTIVITY_INFO_TOKEN = new ActivityInfo();
+ private static final ApplicationInfo NO_APPLICATION_INFO_TOKEN = new ApplicationInfo();
+
+ /**
+ * Callbacks made when manipulating the list.
+ */
+ interface Callbacks {
+ /**
+ * Called when a task is added to the recent tasks list.
+ */
+ void onRecentTaskAdded(TaskRecord task);
+
+ /**
+ * Called when a task is removed from the recent tasks list.
+ */
+ void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed);
+ }
+
/**
* Save recent tasks information across reboots.
*/
private final TaskPersister mTaskPersister;
private final ActivityManagerService mService;
+ private final UserController mUserController;
+
+ /**
+ * Mapping of user id -> whether recent tasks have been loaded for that user.
+ */
private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray(
DEFAULT_INITIAL_CAPACITY);
@@ -81,21 +135,106 @@ class RecentTasks extends ArrayList<TaskRecord> {
* Stores for each user task ids that are taken by tasks residing in persistent storage. These
* tasks may or may not currently be in memory.
*/
- final SparseArray<SparseBooleanArray> mPersistedTaskIds = new SparseArray<>(
+ private final SparseArray<SparseBooleanArray> mPersistedTaskIds = new SparseArray<>(
DEFAULT_INITIAL_CAPACITY);
+ // List of all active recent tasks
+ private final ArrayList<TaskRecord> mTasks = new ArrayList<>();
+ private final ArrayList<Callbacks> mCallbacks = new ArrayList<>();
+
+ // These values are generally loaded from resources, but can be set dynamically in the tests
+ private boolean mHasVisibleRecentTasks;
+ private int mGlobalMaxNumTasks;
+ private int mMinNumVisibleTasks;
+ private int mMaxNumVisibleTasks;
+ private long mActiveTasksSessionDurationMs;
+
// Mainly to avoid object recreation on multiple calls.
- private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>();
+ private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<>();
private final HashMap<ComponentName, ActivityInfo> mTmpAvailActCache = new HashMap<>();
private final HashMap<String, ApplicationInfo> mTmpAvailAppCache = new HashMap<>();
- private final ActivityInfo mTmpActivityInfo = new ActivityInfo();
- private final ApplicationInfo mTmpAppInfo = new ApplicationInfo();
+ private final SparseBooleanArray mTmpQuietProfileUserIds = new SparseBooleanArray();
- RecentTasks(ActivityManagerService service, ActivityStackSupervisor mStackSupervisor) {
- File systemDir = Environment.getDataSystemDirectory();
+ @VisibleForTesting
+ RecentTasks(ActivityManagerService service, TaskPersister taskPersister,
+ UserController userController) {
mService = service;
- mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, service, this);
- mStackSupervisor.setRecentTasks(this);
+ mUserController = userController;
+ mTaskPersister = taskPersister;
+ mGlobalMaxNumTasks = ActivityManager.getMaxRecentTasksStatic();
+ mHasVisibleRecentTasks = true;
+ }
+
+ RecentTasks(ActivityManagerService service, ActivityStackSupervisor stackSupervisor) {
+ final File systemDir = Environment.getDataSystemDirectory();
+ final Resources res = service.mContext.getResources();
+ mService = service;
+ mUserController = service.mUserController;
+ mTaskPersister = new TaskPersister(systemDir, stackSupervisor, service, this);
+ mGlobalMaxNumTasks = ActivityManager.getMaxRecentTasksStatic();
+ mHasVisibleRecentTasks = res.getBoolean(com.android.internal.R.bool.config_hasRecents);
+ loadParametersFromResources(service.mContext.getResources());
+ }
+
+ @VisibleForTesting
+ void setParameters(int minNumVisibleTasks, int maxNumVisibleTasks,
+ long activeSessionDurationMs) {
+ mMinNumVisibleTasks = minNumVisibleTasks;
+ mMaxNumVisibleTasks = maxNumVisibleTasks;
+ mActiveTasksSessionDurationMs = activeSessionDurationMs;
+ }
+
+ @VisibleForTesting
+ void setGlobalMaxNumTasks(int globalMaxNumTasks) {
+ mGlobalMaxNumTasks = globalMaxNumTasks;
+ }
+
+ /**
+ * Loads the parameters from the system resources.
+ */
+ @VisibleForTesting
+ void loadParametersFromResources(Resources res) {
+ if (ActivityManager.isLowRamDeviceStatic()) {
+ mMinNumVisibleTasks = res.getInteger(
+ com.android.internal.R.integer.config_minNumVisibleRecentTasks_lowRam);
+ mMaxNumVisibleTasks = res.getInteger(
+ com.android.internal.R.integer.config_maxNumVisibleRecentTasks_lowRam);
+ } else if (SystemProperties.getBoolean("ro.recents.grid", false)) {
+ mMinNumVisibleTasks = res.getInteger(
+ com.android.internal.R.integer.config_minNumVisibleRecentTasks_grid);
+ mMaxNumVisibleTasks = res.getInteger(
+ com.android.internal.R.integer.config_maxNumVisibleRecentTasks_grid);
+ } else {
+ mMinNumVisibleTasks = res.getInteger(
+ com.android.internal.R.integer.config_minNumVisibleRecentTasks);
+ mMaxNumVisibleTasks = res.getInteger(
+ com.android.internal.R.integer.config_maxNumVisibleRecentTasks);
+ }
+ final int sessionDurationHrs = res.getInteger(
+ com.android.internal.R.integer.config_activeTaskDurationHours);
+ mActiveTasksSessionDurationMs = (sessionDurationHrs > 0)
+ ? TimeUnit.HOURS.toMillis(sessionDurationHrs)
+ : -1;
+ }
+
+ void registerCallback(Callbacks callback) {
+ mCallbacks.add(callback);
+ }
+
+ void unregisterCallback(Callbacks callback) {
+ mCallbacks.remove(callback);
+ }
+
+ private void notifyTaskAdded(TaskRecord task) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onRecentTaskAdded(task);
+ }
+ }
+
+ private void notifyTaskRemoved(TaskRecord task, boolean wasTrimmed) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed);
+ }
}
/**
@@ -106,6 +245,7 @@ class RecentTasks extends ArrayList<TaskRecord> {
*/
void loadUserRecentsLocked(int userId) {
if (mUsersWithRecentsLoaded.get(userId)) {
+ // User already loaded, return early
return;
}
@@ -114,14 +254,14 @@ class RecentTasks extends ArrayList<TaskRecord> {
// Check if any tasks are added before recents is loaded
final SparseBooleanArray preaddedTasks = new SparseBooleanArray();
- for (final TaskRecord task : this) {
+ for (final TaskRecord task : mTasks) {
if (task.userId == userId && shouldPersistTaskLocked(task)) {
preaddedTasks.put(task.taskId, true);
}
}
Slog.i(TAG, "Loading recents for user " + userId + " into memory.");
- addAll(mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks));
+ mTasks.addAll(mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks));
cleanupLocked(userId);
mUsersWithRecentsLoaded.put(userId, true);
@@ -140,11 +280,25 @@ class RecentTasks extends ArrayList<TaskRecord> {
}
}
- boolean taskIdTakenForUserLocked(int taskId, int userId) {
+ /**
+ * @return whether the {@param taskId} is currently in use for the given user.
+ */
+ boolean containsTaskId(int taskId, int userId) {
loadPersistedTaskIdsForUserLocked(userId);
return mPersistedTaskIds.get(userId).get(taskId);
}
+ /**
+ * @return all the task ids for the user with the given {@param userId}.
+ */
+ SparseBooleanArray getTaskIdsForUser(int userId) {
+ loadPersistedTaskIdsForUserLocked(userId);
+ return mPersistedTaskIds.get(userId);
+ }
+
+ /**
+ * Kicks off the task persister to write any pending tasks to disk.
+ */
void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
final ActivityStack stack = task != null ? task.getStack() : null;
if (stack != null && stack.isHomeOrRecentsStack()) {
@@ -164,8 +318,8 @@ class RecentTasks extends ArrayList<TaskRecord> {
mPersistedTaskIds.valueAt(i).clear();
}
}
- for (int i = size() - 1; i >= 0; i--) {
- final TaskRecord task = get(i);
+ for (int i = mTasks.size() - 1; i >= 0; i--) {
+ final TaskRecord task = mTasks.get(i);
if (shouldPersistTaskLocked(task)) {
// Set of persisted taskIds for task.userId should not be null here
// TODO Investigate why it can happen. For now initialize with an empty set
@@ -180,12 +334,12 @@ class RecentTasks extends ArrayList<TaskRecord> {
}
private static boolean shouldPersistTaskLocked(TaskRecord task) {
- final ActivityStack<?> stack = task.getStack();
+ final ActivityStack stack = task.getStack();
return task.isPersistable && (stack == null || !stack.isHomeOrRecentsStack());
}
void onSystemReadyLocked() {
- clear();
+ mTasks.clear();
mTaskPersister.startPersisting();
}
@@ -225,14 +379,6 @@ class RecentTasks extends ArrayList<TaskRecord> {
return usersWithRecentsLoaded;
}
- private void unloadUserRecentsLocked(int userId) {
- if (mUsersWithRecentsLoaded.get(userId)) {
- Slog.i(TAG, "Unloading recents for user " + userId + " from memory.");
- mUsersWithRecentsLoaded.delete(userId);
- removeTasksForUserLocked(userId);
- }
- }
-
/**
* Removes recent tasks and any other state kept in memory for the passed in user. Does not
* touch the information present on persistent storage.
@@ -240,44 +386,36 @@ class RecentTasks extends ArrayList<TaskRecord> {
* @param userId the id of the user
*/
void unloadUserDataFromMemoryLocked(int userId) {
- unloadUserRecentsLocked(userId);
+ if (mUsersWithRecentsLoaded.get(userId)) {
+ Slog.i(TAG, "Unloading recents for user " + userId + " from memory.");
+ mUsersWithRecentsLoaded.delete(userId);
+ removeTasksForUserLocked(userId);
+ }
mPersistedTaskIds.delete(userId);
mTaskPersister.unloadUserDataFromMemory(userId);
}
- TaskRecord taskForIdLocked(int id) {
- final int recentsCount = size();
- for (int i = 0; i < recentsCount; i++) {
- TaskRecord tr = get(i);
- if (tr.taskId == id) {
- return tr;
- }
- }
- return null;
- }
-
/** Remove recent tasks for a user. */
- void removeTasksForUserLocked(int userId) {
+ private void removeTasksForUserLocked(int userId) {
if(userId <= 0) {
Slog.i(TAG, "Can't remove recent task on user " + userId);
return;
}
- for (int i = size() - 1; i >= 0; --i) {
- TaskRecord tr = get(i);
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ TaskRecord tr = mTasks.get(i);
if (tr.userId == userId) {
if(DEBUG_TASKS) Slog.i(TAG_TASKS,
"remove RecentTask " + tr + " when finishing user" + userId);
- remove(i);
- tr.removedFromRecents();
+ remove(mTasks.get(i));
}
}
}
void onPackagesSuspendedChanged(String[] packages, boolean suspended, int userId) {
final Set<String> packageNames = Sets.newHashSet(packages);
- for (int i = size() - 1; i >= 0; --i) {
- final TaskRecord tr = get(i);
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final TaskRecord tr = mTasks.get(i);
if (tr.realActivity != null
&& packageNames.contains(tr.realActivity.getPackageName())
&& tr.userId == userId
@@ -286,7 +424,36 @@ class RecentTasks extends ArrayList<TaskRecord> {
notifyTaskPersisterLocked(tr, false);
}
}
+ }
+
+ void removeTasksByPackageName(String packageName, int userId) {
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final TaskRecord tr = mTasks.get(i);
+ final String taskPackageName =
+ tr.getBaseIntent().getComponent().getPackageName();
+ if (tr.userId != userId) return;
+ if (!taskPackageName.equals(packageName)) return;
+
+ mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
+ }
+ }
+
+ void cleanupDisabledPackageTasksLocked(String packageName, Set<String> filterByClasses,
+ int userId) {
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final TaskRecord tr = mTasks.get(i);
+ if (userId != UserHandle.USER_ALL && tr.userId != userId) {
+ continue;
+ }
+ ComponentName cn = tr.intent.getComponent();
+ final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName)
+ && (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
+ if (sameComponent) {
+ mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
+ REMOVE_FROM_RECENTS);
+ }
+ }
}
/**
@@ -295,24 +462,28 @@ class RecentTasks extends ArrayList<TaskRecord> {
* of affiliations.
*/
void cleanupLocked(int userId) {
- int recentsCount = size();
+ int recentsCount = mTasks.size();
if (recentsCount == 0) {
// Happens when called from the packagemanager broadcast before boot,
// or just any empty list.
return;
}
+ // Clear the temp lists
+ mTmpAvailActCache.clear();
+ mTmpAvailAppCache.clear();
+
final IPackageManager pm = AppGlobals.getPackageManager();
for (int i = recentsCount - 1; i >= 0; i--) {
- final TaskRecord task = get(i);
+ final TaskRecord task = mTasks.get(i);
if (userId != UserHandle.USER_ALL && task.userId != userId) {
// Only look at tasks for the user ID of interest.
continue;
}
if (task.autoRemoveRecents && task.getTopActivity() == null) {
// This situation is broken, and we should just get rid of it now.
- remove(i);
- task.removedFromRecents();
+ mTasks.remove(i);
+ notifyTaskRemoved(task, !TRIMMED);
Slog.w(TAG, "Removing auto-remove without activity: " + task);
continue;
}
@@ -331,11 +502,11 @@ class RecentTasks extends ArrayList<TaskRecord> {
continue;
}
if (ai == null) {
- ai = mTmpActivityInfo;
+ ai = NO_ACTIVITY_INFO_TOKEN;
}
mTmpAvailActCache.put(task.realActivity, ai);
}
- if (ai == mTmpActivityInfo) {
+ if (ai == NO_ACTIVITY_INFO_TOKEN) {
// This could be either because the activity no longer exists, or the
// app is temporarily gone. For the former we want to remove the recents
// entry; for the latter we want to mark it as unavailable.
@@ -350,15 +521,15 @@ class RecentTasks extends ArrayList<TaskRecord> {
continue;
}
if (app == null) {
- app = mTmpAppInfo;
+ app = NO_APPLICATION_INFO_TOKEN;
}
mTmpAvailAppCache.put(task.realActivity.getPackageName(), app);
}
- if (app == mTmpAppInfo
+ if (app == NO_APPLICATION_INFO_TOKEN
|| (app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
// Doesn't exist any more! Good-bye.
- remove(i);
- task.removedFromRecents();
+ mTasks.remove(i);
+ notifyTaskRemoved(task, !TRIMMED);
Slog.w(TAG, "Removing no longer valid recent: " + task);
continue;
} else {
@@ -390,121 +561,197 @@ class RecentTasks extends ArrayList<TaskRecord> {
// Verify the affiliate chain for each task.
int i = 0;
- recentsCount = size();
+ recentsCount = mTasks.size();
while (i < recentsCount) {
i = processNextAffiliateChainLocked(i);
}
// recent tasks are now in sorted, affiliated order.
}
- private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) {
- int recentsCount = size();
- TaskRecord top = task;
- int topIndex = taskIndex;
- while (top.mNextAffiliate != null && topIndex > 0) {
- top = top.mNextAffiliate;
- topIndex--;
+ /**
+ * @return whether the given {@param task} can be added to the list without causing another
+ * task to be trimmed as a result of that add.
+ */
+ private boolean canAddTaskWithoutTrim(TaskRecord task) {
+ return findTrimIndexForAddTask(task) == -1;
+ }
+
+ /**
+ * Returns the list of {@link ActivityManager.AppTask}s.
+ */
+ ArrayList<IBinder> getAppTasksList(int callingUid, String callingPackage) {
+ final ArrayList<IBinder> list = new ArrayList<>();
+ final int size = mTasks.size();
+ for (int i = 0; i < size; i++) {
+ final TaskRecord tr = mTasks.get(i);
+ // Skip tasks that do not match the caller. We don't need to verify
+ // callingPackage, because we are also limiting to callingUid and know
+ // that will limit to the correct security sandbox.
+ if (tr.effectiveUid != callingUid) {
+ continue;
+ }
+ Intent intent = tr.getBaseIntent();
+ if (intent == null || !callingPackage.equals(intent.getComponent().getPackageName())) {
+ continue;
+ }
+ ActivityManager.RecentTaskInfo taskInfo = createRecentTaskInfo(tr);
+ AppTaskImpl taskImpl = new AppTaskImpl(mService, taskInfo.persistentId, callingUid);
+ list.add(taskImpl.asBinder());
}
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding affilliates starting at "
- + topIndex + " from intial " + taskIndex);
- // Find the end of the chain, doing a sanity check along the way.
- boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId;
- int endIndex = topIndex;
- TaskRecord prev = top;
- while (endIndex < recentsCount) {
- TaskRecord cur = get(endIndex);
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: looking at next chain @"
- + endIndex + " " + cur);
- if (cur == top) {
- // Verify start of the chain.
- if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) {
- Slog.wtf(TAG, "Bad chain @" + endIndex
- + ": first task has next affiliate: " + prev);
- sane = false;
- break;
+ return list;
+ }
+
+ /**
+ * @return the list of recent tasks for presentation.
+ */
+ ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags,
+ boolean getTasksAllowed, boolean getDetailedTasks, int userId, int callingUid) {
+ final boolean withExcluded = (flags & RECENT_WITH_EXCLUDED) != 0;
+
+ if (!mService.isUserRunning(userId, FLAG_AND_UNLOCKED)) {
+ Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents");
+ return ParceledListSlice.emptyList();
+ }
+ loadUserRecentsLocked(userId);
+
+ final Set<Integer> includedUsers = mUserController.getProfileIds(userId);
+ includedUsers.add(Integer.valueOf(userId));
+
+ final ArrayList<ActivityManager.RecentTaskInfo> res = new ArrayList<>();
+ final int size = mTasks.size();
+ int numVisibleTasks = 0;
+ for (int i = 0; i < size; i++) {
+ final TaskRecord tr = mTasks.get(i);
+
+ if (isVisibleRecentTask(tr)) {
+ numVisibleTasks++;
+ if (isInVisibleRange(tr, numVisibleTasks)) {
+ // Fall through
+ } else {
+ // Not in visible range
+ continue;
}
} else {
- // Verify middle of the chain's next points back to the one before.
- if (cur.mNextAffiliate != prev
- || cur.mNextAffiliateTaskId != prev.taskId) {
- Slog.wtf(TAG, "Bad chain @" + endIndex
- + ": middle task " + cur + " @" + endIndex
- + " has bad next affiliate "
- + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId
- + ", expected " + prev);
- sane = false;
- break;
- }
+ // Not visible
+ continue;
}
- if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) {
- // Chain ends here.
- if (cur.mPrevAffiliate != null) {
- Slog.wtf(TAG, "Bad chain @" + endIndex
- + ": last task " + cur + " has previous affiliate "
- + cur.mPrevAffiliate);
- sane = false;
- }
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: end of chain @" + endIndex);
- break;
- } else {
- // Verify middle of the chain's prev points to a valid item.
- if (cur.mPrevAffiliate == null) {
- Slog.wtf(TAG, "Bad chain @" + endIndex
- + ": task " + cur + " has previous affiliate "
- + cur.mPrevAffiliate + " but should be id "
- + cur.mPrevAffiliate);
- sane = false;
- break;
- }
+
+ // Skip remaining tasks once we reach the requested size
+ if (res.size() >= maxNum) {
+ continue;
}
- if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) {
- Slog.wtf(TAG, "Bad chain @" + endIndex
- + ": task " + cur + " has affiliated id "
- + cur.mAffiliatedTaskId + " but should be "
- + task.mAffiliatedTaskId);
- sane = false;
- break;
+
+ // Only add calling user or related users recent tasks
+ if (!includedUsers.contains(Integer.valueOf(tr.userId))) {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr);
+ continue;
}
- prev = cur;
- endIndex++;
- if (endIndex >= recentsCount) {
- Slog.wtf(TAG, "Bad chain ran off index " + endIndex
- + ": last task " + prev);
- sane = false;
- break;
+
+ if (tr.realActivitySuspended) {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + tr);
+ continue;
}
- }
- if (sane) {
- if (endIndex < taskIndex) {
- Slog.wtf(TAG, "Bad chain @" + endIndex
- + ": did not extend to task " + task + " @" + taskIndex);
- sane = false;
+
+ // Return the entry if desired by the caller. We always return
+ // the first entry, because callers always expect this to be the
+ // foreground app. We may filter others if the caller has
+ // not supplied RECENT_WITH_EXCLUDED and there is some reason
+ // we should exclude the entry.
+
+ if (i == 0
+ || withExcluded
+ || (tr.intent == null)
+ || ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+ == 0)) {
+ if (!getTasksAllowed) {
+ // If the caller doesn't have the GET_TASKS permission, then only
+ // allow them to see a small subset of tasks -- their own and home.
+ if (!tr.isActivityTypeHome() && tr.effectiveUid != callingUid) {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr);
+ continue;
+ }
+ }
+ if (tr.autoRemoveRecents && tr.getTopActivity() == null) {
+ // Don't include auto remove tasks that are finished or finishing.
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+ "Skipping, auto-remove without activity: " + tr);
+ continue;
+ }
+ if ((flags & RECENT_IGNORE_UNAVAILABLE) != 0 && !tr.isAvailable) {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+ "Skipping, unavail real act: " + tr);
+ continue;
+ }
+
+ if (!tr.mUserSetupComplete) {
+ // Don't include task launched while user is not done setting-up.
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+ "Skipping, user setup not complete: " + tr);
+ continue;
+ }
+
+ ActivityManager.RecentTaskInfo rti = RecentTasks.createRecentTaskInfo(tr);
+ if (!getDetailedTasks) {
+ rti.baseIntent.replaceExtras((Bundle)null);
+ }
+
+ res.add(rti);
}
}
- if (sane) {
- // All looks good, we can just move all of the affiliated tasks
- // to the top.
- for (int i=topIndex; i<=endIndex; i++) {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task
- + " from " + i + " to " + (i-topIndex));
- TaskRecord cur = remove(i);
- add(i - topIndex, cur);
+ return new ParceledListSlice<>(res);
+ }
+
+ /**
+ * @return the list of persistable task ids.
+ */
+ void getPersistableTaskIds(ArraySet<Integer> persistentTaskIds) {
+ final int size = mTasks.size();
+ for (int i = 0; i < size; i++) {
+ final TaskRecord task = mTasks.get(i);
+ if (TaskPersister.DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task
+ + " persistable=" + task.isPersistable);
+ final ActivityStack stack = task.getStack();
+ if ((task.isPersistable || task.inRecents)
+ && (stack == null || !stack.isHomeOrRecentsStack())) {
+ if (TaskPersister.DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
+ persistentTaskIds.add(task.taskId);
+ } else {
+ if (TaskPersister.DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task="
+ + task);
}
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: done moving tasks " + topIndex
- + " to " + endIndex);
- return true;
}
+ }
- // Whoops, couldn't do it.
- return false;
+ @VisibleForTesting
+ ArrayList<TaskRecord> getRawTasks() {
+ return mTasks;
+ }
+
+ /**
+ * @return the task in the task list with the given {@param id} if one exists.
+ */
+ TaskRecord getTask(int id) {
+ final int recentsCount = mTasks.size();
+ for (int i = 0; i < recentsCount; i++) {
+ TaskRecord tr = mTasks.get(i);
+ if (tr.taskId == id) {
+ return tr;
+ }
+ }
+ return null;
}
- final void addLocked(TaskRecord task) {
+ /**
+ * Add a new task to the recent tasks list.
+ */
+ void add(TaskRecord task) {
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "add: task=" + task);
+
final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId
|| task.mNextAffiliateTaskId != INVALID_TASK_ID
|| task.mPrevAffiliateTaskId != INVALID_TASK_ID;
- int recentsCount = size();
+ int recentsCount = mTasks.size();
// Quick case: never add voice sessions.
// TODO: VI what about if it's just an activity?
// Probably nothing to do here
@@ -514,15 +761,15 @@ class RecentTasks extends ArrayList<TaskRecord> {
return;
}
// Another quick case: check if the top-most recent task is the same.
- if (!isAffiliated && recentsCount > 0 && get(0) == task) {
+ if (!isAffiliated && recentsCount > 0 && mTasks.get(0) == task) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: already at top: " + task);
return;
}
// Another quick case: check if this is part of a set of affiliated
// tasks that are at the top.
if (isAffiliated && recentsCount > 0 && task.inRecents
- && task.mAffiliatedTaskId == get(0).mAffiliatedTaskId) {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + get(0)
+ && task.mAffiliatedTaskId == mTasks.get(0).mAffiliatedTaskId) {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + mTasks.get(0)
+ " at top when adding " + task);
return;
}
@@ -532,12 +779,12 @@ class RecentTasks extends ArrayList<TaskRecord> {
// Slightly less quick case: the task is already in recents, so all we need
// to do is move it.
if (task.inRecents) {
- int taskIndex = indexOf(task);
+ int taskIndex = mTasks.indexOf(task);
if (taskIndex >= 0) {
- if (!isAffiliated || MOVE_AFFILIATED_TASKS_TO_FRONT) {
+ if (!isAffiliated || !MOVE_AFFILIATED_TASKS_TO_FRONT) {
// Simple case: this is not an affiliated task, so we just move it to the front.
- remove(taskIndex);
- add(0, task);
+ mTasks.remove(taskIndex);
+ mTasks.add(0, task);
notifyTaskPersisterLocked(task, false);
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
+ " from " + taskIndex);
@@ -560,20 +807,14 @@ class RecentTasks extends ArrayList<TaskRecord> {
}
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task);
- trimForTaskLocked(task, true);
+ trimForAddTask(task);
- recentsCount = size();
- final int maxRecents = ActivityManager.getMaxRecentTasksStatic();
- while (recentsCount >= maxRecents) {
- final TaskRecord tr = remove(recentsCount - 1);
- tr.removedFromRecents();
- recentsCount--;
- }
task.inRecents = true;
if (!isAffiliated || needAffiliationFix) {
// If this is a simple non-affiliated task, or we had some failure trying to
// handle it as part of an affilated task, then just place it at the top.
- add(0, task);
+ mTasks.add(0, task);
+ notifyTaskAdded(task);
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task);
} else if (isAffiliated) {
// If this is a new affiliated task, then move all of the affiliated tasks
@@ -583,7 +824,7 @@ class RecentTasks extends ArrayList<TaskRecord> {
other = task.mPrevAffiliate;
}
if (other != null) {
- int otherIndex = indexOf(other);
+ int otherIndex = mTasks.indexOf(other);
if (otherIndex >= 0) {
// Insert new task at appropriate location.
int taskIndex;
@@ -598,7 +839,8 @@ class RecentTasks extends ArrayList<TaskRecord> {
}
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
"addRecent: new affiliated task added at " + taskIndex + ": " + task);
- add(taskIndex, task);
+ mTasks.add(taskIndex, task);
+ notifyTaskAdded(task);
// Now move everything to the front.
if (moveAffiliatedTasksToFront(task, taskIndex)) {
@@ -625,21 +867,235 @@ class RecentTasks extends ArrayList<TaskRecord> {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: regrouping affiliations");
cleanupLocked(task.userId);
}
+
+ // Trim the set of tasks to the active set
+ trimInactiveRecentTasks();
+ }
+
+ /**
+ * Add the task to the bottom if possible.
+ */
+ boolean addToBottom(TaskRecord task) {
+ if (!canAddTaskWithoutTrim(task)) {
+ // Adding this task would cause the task to be removed (since it's appended at
+ // the bottom and would be trimmed) so just return now
+ return false;
+ }
+
+ add(task);
+ return true;
+ }
+
+ /**
+ * Remove a task from the recent tasks list.
+ */
+ void remove(TaskRecord task) {
+ mTasks.remove(task);
+ notifyTaskRemoved(task, !TRIMMED);
+ }
+
+ /**
+ * Trims the recents task list to the global max number of recents.
+ */
+ private void trimInactiveRecentTasks() {
+ int recentsCount = mTasks.size();
+
+ // Remove from the end of the list until we reach the max number of recents
+ while (recentsCount > mGlobalMaxNumTasks) {
+ final TaskRecord tr = mTasks.remove(recentsCount - 1);
+ notifyTaskRemoved(tr, TRIMMED);
+ recentsCount--;
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming over max-recents task=" + tr
+ + " max=" + mGlobalMaxNumTasks);
+ }
+
+ // Remove any tasks that belong to currently quiet profiles
+ final int[] profileUserIds = mUserController.getCurrentProfileIds();
+ mTmpQuietProfileUserIds.clear();
+ for (int userId : profileUserIds) {
+ final UserInfo userInfo = mUserController.getUserInfo(userId);
+ if (userInfo.isManagedProfile() && userInfo.isQuietModeEnabled()) {
+ mTmpQuietProfileUserIds.put(userId, true);
+ }
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "User: " + userInfo
+ + " quiet=" + mTmpQuietProfileUserIds.get(userId));
+ }
+
+ // Remove any inactive tasks, calculate the latest set of visible tasks
+ int numVisibleTasks = 0;
+ for (int i = 0; i < mTasks.size();) {
+ final TaskRecord task = mTasks.get(i);
+
+ if (isActiveRecentTask(task, mTmpQuietProfileUserIds)) {
+ if (!mHasVisibleRecentTasks) {
+ // Keep all active tasks if visible recent tasks is not supported
+ i++;
+ continue;
+ }
+
+ if (!isVisibleRecentTask(task)) {
+ // Keep all active-but-invisible tasks
+ i++;
+ continue;
+ } else {
+ numVisibleTasks++;
+ if (isInVisibleRange(task, numVisibleTasks)) {
+ // Keep visible tasks in range
+ i++;
+ continue;
+ } else {
+ // Fall through to trim visible tasks that are no longer in range
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
+ "Trimming out-of-range visible task=" + task);
+ }
+ }
+ } else {
+ // Fall through to trim inactive tasks
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming inactive task=" + task);
+ }
+
+ // Task is no longer active, trim it from the list
+ mTasks.remove(task);
+ notifyTaskRemoved(task, TRIMMED);
+ notifyTaskPersisterLocked(task, false /* flush */);
+ }
+ }
+
+ /**
+ * @return whether the given task should be considered active.
+ */
+ private boolean isActiveRecentTask(TaskRecord task, SparseBooleanArray quietProfileUserIds) {
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "isActiveRecentTask: task=" + task
+ + " globalMax=" + mGlobalMaxNumTasks);
+
+ if (quietProfileUserIds.get(task.userId)) {
+ // Quiet profile user's tasks are never active
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\tisQuietProfileTask=true");
+ return false;
+ }
+
+ if (task.mAffiliatedTaskId != INVALID_TASK_ID && task.mAffiliatedTaskId != task.taskId) {
+ // Keep the task active if its affiliated task is also active
+ final TaskRecord affiliatedTask = getTask(task.mAffiliatedTaskId);
+ if (affiliatedTask != null) {
+ if (!isActiveRecentTask(affiliatedTask, quietProfileUserIds)) {
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
+ "\taffiliatedWithTask=" + affiliatedTask + " is not active");
+ return false;
+ }
+ }
+ }
+
+ // All other tasks are considered active
+ return true;
+ }
+
+ /**
+ * @return whether the given active task should be presented to the user through SystemUI.
+ */
+ private boolean isVisibleRecentTask(TaskRecord task) {
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "isVisibleRecentTask: task=" + task
+ + " minVis=" + mMinNumVisibleTasks + " maxVis=" + mMaxNumVisibleTasks
+ + " sessionDuration=" + mActiveTasksSessionDurationMs
+ + " inactiveDuration=" + task.getInactiveDuration()
+ + " activityType=" + task.getActivityType()
+ + " windowingMode=" + task.getWindowingMode());
+
+ // Ignore certain activity types completely
+ switch (task.getActivityType()) {
+ case ACTIVITY_TYPE_HOME:
+ case ACTIVITY_TYPE_RECENTS:
+ return false;
+ }
+
+ // Ignore certain windowing modes
+ switch (task.getWindowingMode()) {
+ case WINDOWING_MODE_PINNED:
+ return false;
+ case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\ttop=" + task.getStack().topTask());
+ final ActivityStack stack = task.getStack();
+ if (stack != null && stack.topTask() == task) {
+ // Only the non-top task of the primary split screen mode is visible
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @return whether the given visible task is within the policy range.
+ */
+ private boolean isInVisibleRange(TaskRecord task, int numVisibleTasks) {
+ // Keep the last most task even if it is excluded from recents
+ final boolean isExcludeFromRecents =
+ (task.getBaseIntent().getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+ == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+ if (isExcludeFromRecents) {
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\texcludeFromRecents=true");
+ return numVisibleTasks == 1;
+ }
+
+ if (mMinNumVisibleTasks >= 0 && numVisibleTasks <= mMinNumVisibleTasks) {
+ // Always keep up to the min number of recent tasks, after that fall through to the
+ // checks below
+ return true;
+ }
+
+ if (mMaxNumVisibleTasks >= 0) {
+ // Always keep up to the max number of recent tasks, but return false afterwards
+ return numVisibleTasks <= mMaxNumVisibleTasks;
+ }
+
+ if (mActiveTasksSessionDurationMs > 0) {
+ // Keep the task if the inactive time is within the session window, this check must come
+ // after the checks for the min/max visible task range
+ if (task.getInactiveDuration() <= mActiveTasksSessionDurationMs) {
+ return true;
+ }
+ }
+
+ return false;
}
/**
* If needed, remove oldest existing entries in recents that are for the same kind
* of task as the given one.
*/
- int trimForTaskLocked(TaskRecord task, boolean doTrim) {
- int recentsCount = size();
+ private void trimForAddTask(TaskRecord task) {
+ final int removeIndex = findTrimIndexForAddTask(task);
+ if (removeIndex == -1) {
+ // Nothing to trim
+ return;
+ }
+
+ // There is a similar task that will be removed for the addition of {@param task}, but it
+ // can be the same task, and if so, the task will be re-added in add(), so skip the
+ // callbacks here.
+ final TaskRecord removedTask = mTasks.remove(removeIndex);
+ if (removedTask != task) {
+ notifyTaskRemoved(removedTask, TRIMMED);
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming task=" + removedTask
+ + " for addition of task=" + task);
+ }
+ notifyTaskPersisterLocked(removedTask, false /* flush */);
+ }
+
+ /**
+ * Find the task that would be removed if the given {@param task} is added to the recent tasks
+ * list (if any).
+ */
+ private int findTrimIndexForAddTask(TaskRecord task) {
+ int recentsCount = mTasks.size();
final Intent intent = task.intent;
final boolean document = intent != null && intent.isDocument();
int maxRecents = task.maxRecents - 1;
final ActivityStack stack = task.getStack();
for (int i = 0; i < recentsCount; i++) {
- final TaskRecord tr = get(i);
+ final TaskRecord tr = mTasks.get(i);
final ActivityStack trStack = tr.getStack();
+
if (task != tr) {
if (stack != null && trStack != null && stack != trStack) {
continue;
@@ -650,7 +1106,7 @@ class RecentTasks extends ArrayList<TaskRecord> {
final Intent trIntent = tr.intent;
final boolean sameAffinity =
task.affinity != null && task.affinity.equals(tr.affinity);
- final boolean sameIntentFilter = intent != null && intent.filterEquals(trIntent);
+ final boolean sameIntent = intent != null && intent.filterEquals(trIntent);
boolean multiTasksAllowed = false;
final int flags = intent.getFlags();
if ((flags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT)) != 0
@@ -659,7 +1115,7 @@ class RecentTasks extends ArrayList<TaskRecord> {
}
final boolean trIsDocument = trIntent != null && trIntent.isDocument();
final boolean bothDocuments = document && trIsDocument;
- if (!sameAffinity && !sameIntentFilter && !bothDocuments) {
+ if (!sameAffinity && !sameIntent && !bothDocuments) {
continue;
}
@@ -668,17 +1124,17 @@ class RecentTasks extends ArrayList<TaskRecord> {
final boolean sameActivity = task.realActivity != null
&& tr.realActivity != null
&& task.realActivity.equals(tr.realActivity);
- // If the document is open in another app or is not the same
- // document, we don't need to trim it.
if (!sameActivity) {
+ // If the document is open in another app or is not the same document, we
+ // don't need to trim it.
continue;
- // Otherwise only trim if we are over our max recents for this task
} else if (maxRecents > 0) {
+ // Otherwise only trim if we are over our max recents for this task
--maxRecents;
- if (!doTrim || !sameIntentFilter || multiTasksAllowed) {
+ if (!sameIntent || multiTasksAllowed) {
// We don't want to trim if we are not over the max allowed entries and
- // the caller doesn't want us to trim, the tasks are not of the same
- // intent filter, or multiple entries fot the task is allowed.
+ // the tasks are not of the same intent filter, or multiple entries for
+ // the task is allowed.
continue;
}
}
@@ -689,44 +1145,14 @@ class RecentTasks extends ArrayList<TaskRecord> {
continue;
}
}
-
- if (!doTrim) {
- // If the caller is not actually asking for a trim, just tell them we reached
- // a point where the trim would happen.
- return i;
- }
-
- // Either task and tr are the same or, their affinities match or their intents match
- // and neither of them is a document, or they are documents using the same activity
- // and their maxRecents has been reached.
- remove(i);
- if (task != tr) {
- tr.removedFromRecents();
- }
- i--;
- recentsCount--;
- if (task.intent == null) {
- // If the new recent task we are adding is not fully
- // specified, then replace it with the existing recent task.
- task = tr;
- }
- notifyTaskPersisterLocked(tr, false);
+ return i;
}
-
return -1;
}
- // Sort by taskId
- private static Comparator<TaskRecord> sTaskRecordComparator = new Comparator<TaskRecord>() {
- @Override
- public int compare(TaskRecord lhs, TaskRecord rhs) {
- return rhs.taskId - lhs.taskId;
- }
- };
-
// Extract the affiliates of the chain containing recent at index start.
private int processNextAffiliateChainLocked(int start) {
- final TaskRecord startTask = get(start);
+ final TaskRecord startTask = mTasks.get(start);
final int affiliateId = startTask.mAffiliatedTaskId;
// Quick identification of isolated tasks. I.e. those not launched behind.
@@ -741,17 +1167,17 @@ class RecentTasks extends ArrayList<TaskRecord> {
// Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents.
mTmpRecents.clear();
- for (int i = size() - 1; i >= start; --i) {
- final TaskRecord task = get(i);
+ for (int i = mTasks.size() - 1; i >= start; --i) {
+ final TaskRecord task = mTasks.get(i);
if (task.mAffiliatedTaskId == affiliateId) {
- remove(i);
+ mTasks.remove(i);
mTmpRecents.add(task);
}
}
// Sort them all by taskId. That is the order they were create in and that order will
// always be correct.
- Collections.sort(mTmpRecents, sTaskRecordComparator);
+ Collections.sort(mTmpRecents, TASK_ID_COMPARATOR);
// Go through and fix up the linked list.
// The first one is the end of the chain and has no next.
@@ -789,11 +1215,197 @@ class RecentTasks extends ArrayList<TaskRecord> {
notifyTaskPersisterLocked(last, false);
}
- // Insert the group back into mRecentTasks at start.
- addAll(start, mTmpRecents);
+ // Insert the group back into mTmpTasks at start.
+ mTasks.addAll(start, mTmpRecents);
mTmpRecents.clear();
// Let the caller know where we left off.
return start + tmpSize;
}
+
+ private boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) {
+ int recentsCount = mTasks.size();
+ TaskRecord top = task;
+ int topIndex = taskIndex;
+ while (top.mNextAffiliate != null && topIndex > 0) {
+ top = top.mNextAffiliate;
+ topIndex--;
+ }
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding affilliates starting at "
+ + topIndex + " from intial " + taskIndex);
+ // Find the end of the chain, doing a sanity check along the way.
+ boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId;
+ int endIndex = topIndex;
+ TaskRecord prev = top;
+ while (endIndex < recentsCount) {
+ TaskRecord cur = mTasks.get(endIndex);
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: looking at next chain @"
+ + endIndex + " " + cur);
+ if (cur == top) {
+ // Verify start of the chain.
+ if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) {
+ Slog.wtf(TAG, "Bad chain @" + endIndex
+ + ": first task has next affiliate: " + prev);
+ sane = false;
+ break;
+ }
+ } else {
+ // Verify middle of the chain's next points back to the one before.
+ if (cur.mNextAffiliate != prev
+ || cur.mNextAffiliateTaskId != prev.taskId) {
+ Slog.wtf(TAG, "Bad chain @" + endIndex
+ + ": middle task " + cur + " @" + endIndex
+ + " has bad next affiliate "
+ + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId
+ + ", expected " + prev);
+ sane = false;
+ break;
+ }
+ }
+ if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) {
+ // Chain ends here.
+ if (cur.mPrevAffiliate != null) {
+ Slog.wtf(TAG, "Bad chain @" + endIndex
+ + ": last task " + cur + " has previous affiliate "
+ + cur.mPrevAffiliate);
+ sane = false;
+ }
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: end of chain @" + endIndex);
+ break;
+ } else {
+ // Verify middle of the chain's prev points to a valid item.
+ if (cur.mPrevAffiliate == null) {
+ Slog.wtf(TAG, "Bad chain @" + endIndex
+ + ": task " + cur + " has previous affiliate "
+ + cur.mPrevAffiliate + " but should be id "
+ + cur.mPrevAffiliate);
+ sane = false;
+ break;
+ }
+ }
+ if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) {
+ Slog.wtf(TAG, "Bad chain @" + endIndex
+ + ": task " + cur + " has affiliated id "
+ + cur.mAffiliatedTaskId + " but should be "
+ + task.mAffiliatedTaskId);
+ sane = false;
+ break;
+ }
+ prev = cur;
+ endIndex++;
+ if (endIndex >= recentsCount) {
+ Slog.wtf(TAG, "Bad chain ran off index " + endIndex
+ + ": last task " + prev);
+ sane = false;
+ break;
+ }
+ }
+ if (sane) {
+ if (endIndex < taskIndex) {
+ Slog.wtf(TAG, "Bad chain @" + endIndex
+ + ": did not extend to task " + task + " @" + taskIndex);
+ sane = false;
+ }
+ }
+ if (sane) {
+ // All looks good, we can just move all of the affiliated tasks
+ // to the top.
+ for (int i=topIndex; i<=endIndex; i++) {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task
+ + " from " + i + " to " + (i-topIndex));
+ TaskRecord cur = mTasks.remove(i);
+ mTasks.add(i - topIndex, cur);
+ }
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: done moving tasks " + topIndex
+ + " to " + endIndex);
+ return true;
+ }
+
+ // Whoops, couldn't do it.
+ return false;
+ }
+
+ void dump(PrintWriter pw, boolean dumpAll, String dumpPackage) {
+ pw.println("ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)");
+ if (mTasks.isEmpty()) {
+ return;
+ }
+
+ final MutableBoolean printedAnything = new MutableBoolean(false);
+ final MutableBoolean printedHeader = new MutableBoolean(false);
+ final int size = mTasks.size();
+ for (int i = 0; i < size; i++) {
+ final TaskRecord tr = mTasks.get(i);
+ if (dumpPackage != null && (tr.realActivity == null ||
+ !dumpPackage.equals(tr.realActivity.getPackageName()))) {
+ continue;
+ }
+
+ if (!printedHeader.value) {
+ pw.println(" Recent tasks:");
+ printedHeader.value = true;
+ printedAnything.value = true;
+ }
+ pw.print(" * Recent #"); pw.print(i); pw.print(": ");
+ pw.println(tr);
+ if (dumpAll) {
+ tr.dump(pw, " ");
+ }
+ }
+
+ if (!printedAnything.value) {
+ pw.println(" (nothing)");
+ }
+ }
+
+ /**
+ * Creates a new RecentTaskInfo from a TaskRecord.
+ */
+ static ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) {
+ // Update the task description to reflect any changes in the task stack
+ tr.updateTaskDescription();
+
+ // Compose the recent task info
+ ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
+ rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId;
+ rti.persistentId = tr.taskId;
+ rti.baseIntent = new Intent(tr.getBaseIntent());
+ rti.origActivity = tr.origActivity;
+ rti.realActivity = tr.realActivity;
+ rti.description = tr.lastDescription;
+ rti.stackId = tr.getStackId();
+ rti.userId = tr.userId;
+ rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
+ rti.lastActiveTime = tr.lastActiveTime;
+ rti.affiliatedTaskId = tr.mAffiliatedTaskId;
+ rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
+ rti.numActivities = 0;
+ if (tr.mBounds != null) {
+ rti.bounds = new Rect(tr.mBounds);
+ }
+ rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode();
+ rti.resizeMode = tr.mResizeMode;
+ rti.configuration.setTo(tr.getConfiguration());
+
+ ActivityRecord base = null;
+ ActivityRecord top = null;
+ ActivityRecord tmp;
+
+ for (int i = tr.mActivities.size() - 1; i >= 0; --i) {
+ tmp = tr.mActivities.get(i);
+ if (tmp.finishing) {
+ continue;
+ }
+ base = tmp;
+ if (top == null || (top.state == ActivityState.INITIALIZING)) {
+ top = base;
+ }
+ rti.numActivities++;
+ }
+
+ rti.baseActivity = (base != null) ? base.intent.getComponent() : null;
+ rti.topActivity = (top != null) ? top.intent.getComponent() : null;
+
+ return rti;
+ }
}
diff --git a/com/android/server/am/ServiceRecord.java b/com/android/server/am/ServiceRecord.java
index ac85e6b1..16995e50 100644
--- a/com/android/server/am/ServiceRecord.java
+++ b/com/android/server/am/ServiceRecord.java
@@ -33,6 +33,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -517,14 +518,27 @@ final class ServiceRecord extends Binder {
} catch (PackageManager.NameNotFoundException e) {
}
}
- if (localForegroundNoti.getSmallIcon() == null
- || nm.getNotificationChannel(localPackageName, appUid,
+ if (nm.getNotificationChannel(localPackageName, appUid,
localForegroundNoti.getChannelId()) == null) {
+ int targetSdkVersion = Build.VERSION_CODES.O_MR1;
+ try {
+ final ApplicationInfo applicationInfo =
+ ams.mContext.getPackageManager().getApplicationInfoAsUser(
+ appInfo.packageName, 0, userId);
+ targetSdkVersion = applicationInfo.targetSdkVersion;
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ if (targetSdkVersion >= Build.VERSION_CODES.O_MR1) {
+ throw new RuntimeException(
+ "invalid channel for service notification: "
+ + foregroundNoti);
+ }
+ }
+ if (localForegroundNoti.getSmallIcon() == 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/TaskPersister.java b/com/android/server/am/TaskPersister.java
index 61994b55..2689d6a4 100644
--- a/com/android/server/am/TaskPersister.java
+++ b/com/android/server/am/TaskPersister.java
@@ -567,7 +567,7 @@ public class TaskPersister {
SparseArray<SparseBooleanArray> changedTaskIdsPerUser = new SparseArray<>();
synchronized (mService) {
for (int userId : mRecentTasks.usersWithRecentsLoadedLocked()) {
- SparseBooleanArray taskIdsToSave = mRecentTasks.mPersistedTaskIds.get(userId);
+ SparseBooleanArray taskIdsToSave = mRecentTasks.getTaskIdsForUser(userId);
SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId);
if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIdsToSave)) {
continue;
@@ -640,7 +640,7 @@ public class TaskPersister {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
+ ArraySet<Integer> persistentTaskIds = new ArraySet<>();
while (true) {
// We can't lock mService while holding TaskPersister.this, but we don't want to
// call removeObsoleteFiles every time through the loop, only the last time before
@@ -654,20 +654,7 @@ public class TaskPersister {
persistentTaskIds.clear();
synchronized (mService) {
if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks);
- for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final TaskRecord task = mRecentTasks.get(taskNdx);
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task +
- " persistable=" + task.isPersistable);
- final ActivityStack stack = task.getStack();
- if ((task.isPersistable || task.inRecents)
- && (stack == null || !stack.isHomeOrRecentsStack())) {
- if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
- persistentTaskIds.add(task.taskId);
- } else {
- if (DEBUG) Slog.d(TAG,
- "omitting from persistentTaskIds task=" + task);
- }
- }
+ mRecentTasks.getPersistableTaskIds(persistentTaskIds);
mService.mWindowManager.removeObsoleteTaskFiles(persistentTaskIds,
mRecentTasks.usersWithRecentsLoadedLocked());
}
diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java
index 0d8df796..a1b45a1e 100644
--- a/com/android/server/am/TaskRecord.java
+++ b/com/android/server/am/TaskRecord.java
@@ -18,10 +18,7 @@ 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.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.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;
@@ -31,6 +28,7 @@ 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.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;
@@ -45,7 +43,6 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
-import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -86,7 +83,6 @@ import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
@@ -102,6 +98,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Debug;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
@@ -155,8 +152,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
private static final String ATTR_EFFECTIVE_UID = "effective_uid";
@Deprecated
private static final String ATTR_TASKTYPE = "task_type";
- private static final String ATTR_FIRSTACTIVETIME = "first_active_time";
- private static final String ATTR_LASTACTIVETIME = "last_active_time";
private static final String ATTR_LASTDESCRIPTION = "last_description";
private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
@@ -168,7 +163,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
private static final String ATTR_CALLING_PACKAGE = "calling_package";
private static final String ATTR_SUPPORTS_PICTURE_IN_PICTURE = "supports_picture_in_picture";
private static final String ATTR_RESIZE_MODE = "resize_mode";
- private static final String ATTR_PRIVILEGED = "privileged";
private static final String ATTR_NON_FULLSCREEN_BOUNDS = "non_fullscreen_bounds";
private static final String ATTR_MIN_WIDTH = "min_width";
private static final String ATTR_MIN_HEIGHT = "min_height";
@@ -212,9 +206,10 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
ComponentName realActivity; // The actual activity component that started the task.
boolean realActivitySuspended; // True if the actual activity component that started the
// task is suspended.
- long firstActiveTime; // First time this task was active.
- long lastActiveTime; // Last time this task was active, including sleep.
boolean inRecents; // Actually in the recents list?
+ long lastActiveTime; // Last time this task was active in the current device session,
+ // including sleep. This time is initialized to the elapsed time when
+ // restored from disk.
boolean isAvailable; // Is the activity available to be launched?
boolean rootWasReset; // True if the intent at the root of the task had
// the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
@@ -237,10 +232,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
// of the root activity.
boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize
// changes on a temporary basis.
- private int mLockTaskMode; // Which tasklock mode to launch this task in. One of
- // ActivityManager.LOCK_TASK_LAUNCH_MODE_*
- private boolean mPrivileged; // The root activity application of this task holds
- // privileged permissions.
/** Can't be put in lockTask mode. */
final static int LOCK_TASK_AUTH_DONT_LOCK = 0;
@@ -339,6 +330,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
TaskPersister.IMAGE_EXTENSION;
userId = UserHandle.getUserId(info.applicationInfo.uid);
taskId = _taskId;
+ lastActiveTime = SystemClock.elapsedRealtime();
mAffiliatedTaskId = _taskId;
voiceSession = _voiceSession;
voiceInteractor = _voiceInteractor;
@@ -359,6 +351,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
TaskPersister.IMAGE_EXTENSION;
userId = UserHandle.getUserId(info.applicationInfo.uid);
taskId = _taskId;
+ lastActiveTime = SystemClock.elapsedRealtime();
mAffiliatedTaskId = _taskId;
voiceSession = null;
voiceInteractor = null;
@@ -385,12 +378,11 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId,
int _effectiveUid, String _lastDescription, ArrayList<ActivityRecord> activities,
- long _firstActiveTime, long _lastActiveTime, long lastTimeMoved,
- boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription,
- int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor,
- int callingUid, String callingPackage, int resizeMode, boolean supportsPictureInPicture,
- boolean privileged, boolean _realActivitySuspended, boolean userSetupComplete,
- int minWidth, int minHeight) {
+ long lastTimeMoved, boolean neverRelinquishIdentity,
+ TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId,
+ int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage,
+ int resizeMode, boolean supportsPictureInPicture, boolean _realActivitySuspended,
+ boolean userSetupComplete, int minWidth, int minHeight) {
mService = service;
mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
TaskPersister.IMAGE_EXTENSION;
@@ -412,8 +404,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
userId = _userId;
mUserSetupComplete = userSetupComplete;
effectiveUid = _effectiveUid;
- firstActiveTime = _firstActiveTime;
- lastActiveTime = _lastActiveTime;
+ lastActiveTime = SystemClock.elapsedRealtime();
lastDescription = _lastDescription;
mActivities = activities;
mLastTimeMoved = lastTimeMoved;
@@ -427,7 +418,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
mCallingPackage = callingPackage;
mResizeMode = resizeMode;
mSupportsPictureInPicture = supportsPictureInPicture;
- mPrivileged = privileged;
mMinWidth = minWidth;
mMinHeight = minHeight;
mService.mTaskChangeNotificationController.notifyTaskCreated(_taskId, realActivity);
@@ -520,7 +510,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
// All we can do for now is update the bounds so it can be used when the task is
// added to window manager.
updateOverrideConfiguration(bounds);
- if (getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
+ if (!inFreeformWindowingMode()) {
// re-restore the task so it can have the proper stack association.
mService.mStackSupervisor.restoreRecentTaskLocked(this, null);
}
@@ -616,8 +606,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
* @return whether the task was reparented
*/
// 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} */
+ // re-parenting the task. Can only be done when we are no longer using static stack Ids.
boolean reparent(ActivityStack preferredStack, int position,
@ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
boolean schedulePictureInPictureModeChange, String reason) {
@@ -630,12 +619,12 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
return false;
}
- final int sourceStackId = getStackId();
- final int stackId = toStack.getStackId();
+ final int toStackWindowingMode = toStack.getWindowingMode();
final ActivityRecord topActivity = getTopActivity();
- final boolean mightReplaceWindow = StackId.replaceWindowsOnTaskMove(sourceStackId, stackId)
- && topActivity != null;
+ final boolean mightReplaceWindow =
+ replaceWindowsOnTaskMove(getWindowingMode(), toStackWindowingMode)
+ && topActivity != null;
if (mightReplaceWindow) {
// We are about to relaunch the activity because its configuration changed due to
// being maximized, i.e. size change. The activity will first remove the old window
@@ -660,7 +649,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
// In some cases the focused stack isn't the front stack. E.g. pinned stack.
// Whenever we are moving the top activity from the front stack we want to make sure to
// move the stack to the front.
- final boolean wasFront = r != null && supervisor.isFrontStackOnDisplay(sourceStack)
+ final boolean wasFront = r != null && sourceStack.isTopStackOnDisplay()
&& (sourceStack.topRunningActivityLocked() == r);
// Adjust the position for the new parent stack as needed.
@@ -707,10 +696,10 @@ 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
+ if ((toStackWindowingMode == WINDOWING_MODE_FULLSCREEN
+ || toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)
&& !Objects.equals(mBounds, toStack.mBounds)) {
kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
deferResume);
@@ -749,9 +738,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
// TODO: Handle incorrect request to move before the actual move, not after.
- final boolean inSplitScreenMode = supervisor.getDefaultDisplay().hasSplitScreenStack();
+ final boolean inSplitScreenMode = supervisor.getDefaultDisplay().hasSplitScreenPrimaryStack();
supervisor.handleNonResizableTaskIfNeeded(this, preferredStack.getWindowingMode(),
- DEFAULT_DISPLAY, stackId);
+ DEFAULT_DISPLAY, toStack);
boolean successful = (preferredStack == toStack);
if (successful && toStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
@@ -761,6 +750,18 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
return successful;
}
+ /**
+ * 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.
+ * @hide
+ */
+ private static boolean replaceWindowsOnTaskMove(
+ int sourceWindowingMode, int targetWindowingMode) {
+ return sourceWindowingMode == WINDOWING_MODE_FREEFORM
+ || targetWindowingMode == WINDOWING_MODE_FREEFORM;
+ }
+
void cancelWindowTransition() {
mWindowContainerController.cancelWindowTransition();
}
@@ -780,14 +781,11 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
void touchActiveTime() {
- lastActiveTime = System.currentTimeMillis();
- if (firstActiveTime == 0) {
- firstActiveTime = lastActiveTime;
- }
+ lastActiveTime = SystemClock.elapsedRealtime();
}
long getInactiveDuration() {
- return System.currentTimeMillis() - lastActiveTime;
+ return SystemClock.elapsedRealtime() - lastActiveTime;
}
/** Sets the original intent, and the calling uid and package. */
@@ -795,6 +793,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
mCallingUid = r.launchedFromUid;
mCallingPackage = r.launchedFromPackage;
setIntent(r.intent, r.info);
+ setLockTaskAuth(r);
}
/** Sets the original intent, _without_ updating the calling uid or package. */
@@ -878,14 +877,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
mResizeMode = info.resizeMode;
mSupportsPictureInPicture = info.supportsPictureInPicture();
- 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();
}
/** Sets the original minimal width and height. */
@@ -1263,7 +1254,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
mService.notifyTaskPersisterLocked(this, false);
}
- if (getStackId() == PINNED_STACK_ID) {
+ if (inPinnedWindowingMode()) {
// We normally notify listeners of task stack changes on pause, however pinned stack
// activities are normally in the paused state so no notification will be sent there
// before the activity is removed. We send it here so instead.
@@ -1422,8 +1413,17 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
void setLockTaskAuth() {
+ setLockTaskAuth(getRootActivity());
+ }
+
+ private void setLockTaskAuth(@Nullable ActivityRecord r) {
+ if (r == null) {
+ mLockTaskAuth = LOCK_TASK_AUTH_PINNABLE;
+ return;
+ }
+
final String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
- switch (mLockTaskMode) {
+ switch (r.lockTaskLaunchMode) {
case LOCK_TASK_LAUNCH_MODE_DEFAULT:
mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
? LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
@@ -1493,7 +1493,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
* @return True if the requested bounds are okay for a resizing request.
*/
private boolean canResizeToBounds(Rect bounds) {
- if (bounds == null || getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
+ if (bounds == null || !inFreeformWindowingMode()) {
// Note: If not on the freeform workspace, we ignore the bounds.
return true;
}
@@ -1559,6 +1559,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
// values in the TaskRecord.
String label = null;
String iconFilename = null;
+ int iconResource = -1;
int colorPrimary = 0;
int colorBackground = 0;
int statusBarColor = 0;
@@ -1570,6 +1571,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
if (label == null) {
label = r.taskDescription.getLabel();
}
+ if (iconResource == -1) {
+ iconResource = r.taskDescription.getIconResource();
+ }
if (iconFilename == null) {
iconFilename = r.taskDescription.getIconFilename();
}
@@ -1584,8 +1588,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
topActivity = false;
}
- lastTaskDescription = new TaskDescription(label, null, iconFilename, colorPrimary,
- colorBackground, statusBarColor, navigationBarColor);
+ lastTaskDescription = new TaskDescription(label, null, iconResource, iconFilename,
+ colorPrimary, colorBackground, statusBarColor, navigationBarColor);
if (mWindowContainerController != null) {
mWindowContainerController.setTaskDescription(lastTaskDescription);
}
@@ -1647,8 +1651,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
out.attribute(null, ATTR_USERID, String.valueOf(userId));
out.attribute(null, ATTR_USER_SETUP_COMPLETE, String.valueOf(mUserSetupComplete));
out.attribute(null, ATTR_EFFECTIVE_UID, String.valueOf(effectiveUid));
- out.attribute(null, ATTR_FIRSTACTIVETIME, String.valueOf(firstActiveTime));
- out.attribute(null, ATTR_LASTACTIVETIME, String.valueOf(lastActiveTime));
out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity));
if (lastDescription != null) {
@@ -1666,7 +1668,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
out.attribute(null, ATTR_RESIZE_MODE, String.valueOf(mResizeMode));
out.attribute(null, ATTR_SUPPORTS_PICTURE_IN_PICTURE,
String.valueOf(mSupportsPictureInPicture));
- out.attribute(null, ATTR_PRIVILEGED, String.valueOf(mPrivileged));
if (mLastNonFullscreenBounds != null) {
out.attribute(
null, ATTR_NON_FULLSCREEN_BOUNDS, mLastNonFullscreenBounds.flattenToString());
@@ -1721,8 +1722,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
boolean userSetupComplete = true;
int effectiveUid = -1;
String lastDescription = null;
- long firstActiveTime = -1;
- long lastActiveTime = -1;
long lastTimeOnTop = 0;
boolean neverRelinquishIdentity = true;
int taskId = INVALID_TASK_ID;
@@ -1736,7 +1735,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
String callingPackage = "";
int resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
boolean supportsPictureInPicture = false;
- boolean privileged = false;
Rect bounds = null;
int minWidth = INVALID_MIN_SIZE;
int minHeight = INVALID_MIN_SIZE;
@@ -1774,10 +1772,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
effectiveUid = Integer.parseInt(attrValue);
} else if (ATTR_TASKTYPE.equals(attrName)) {
taskType = Integer.parseInt(attrValue);
- } else if (ATTR_FIRSTACTIVETIME.equals(attrName)) {
- firstActiveTime = Long.parseLong(attrValue);
- } else if (ATTR_LASTACTIVETIME.equals(attrName)) {
- lastActiveTime = Long.parseLong(attrValue);
} else if (ATTR_LASTDESCRIPTION.equals(attrName)) {
lastDescription = attrValue;
} else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
@@ -1802,8 +1796,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
resizeMode = Integer.parseInt(attrValue);
} else if (ATTR_SUPPORTS_PICTURE_IN_PICTURE.equals(attrName)) {
supportsPictureInPicture = Boolean.parseBoolean(attrValue);
- } else if (ATTR_PRIVILEGED.equals(attrName)) {
- privileged = Boolean.parseBoolean(attrValue);
} else if (ATTR_NON_FULLSCREEN_BOUNDS.equals(attrName)) {
bounds = Rect.unflattenFromString(attrValue);
} else if (ATTR_MIN_WIDTH.equals(attrName)) {
@@ -1888,10 +1880,10 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootHasReset,
autoRemoveRecents, askedCompatMode, userId, effectiveUid, lastDescription,
- activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
- taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor,
- callingUid, callingPackage, resizeMode, supportsPictureInPicture, privileged,
- realActivitySuspended, userSetupComplete, minWidth, minHeight);
+ activities, lastTimeOnTop, neverRelinquishIdentity, taskDescription,
+ taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, callingUid,
+ callingPackage, resizeMode, supportsPictureInPicture, realActivitySuspended,
+ userSetupComplete, minWidth, minHeight);
task.updateOverrideConfiguration(bounds);
for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
@@ -1911,7 +1903,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
// If the task has no requested minimal size, we'd like to enforce a minimal size
// so that the user can not render the task too small to manipulate. We don't need
// to do this for the pinned stack as the bounds are controlled by the system.
- if (getStackId() != PINNED_STACK_ID) {
+ if (!inPinnedWindowingMode()) {
if (minWidth == INVALID_MIN_SIZE) {
minWidth = mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask;
}
@@ -2085,7 +2077,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
return;
}
- if (inStack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+ if (inStack.inFreeformWindowingMode()) {
if (!isResizeable()) {
throw new IllegalArgumentException("Can not position non-resizeable task="
+ this + " in stack=" + inStack);
@@ -2220,7 +2212,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
pw.print(" mSupportsPictureInPicture=" + mSupportsPictureInPicture);
pw.print(" isResizeable=" + isResizeable());
- pw.print(" firstActiveTime=" + firstActiveTime);
pw.print(" lastActiveTime=" + lastActiveTime);
pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
}
diff --git a/com/android/server/appwidget/AppWidgetServiceImpl.java b/com/android/server/appwidget/AppWidgetServiceImpl.java
index a6aaaa67..51afada2 100644
--- a/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -71,7 +71,6 @@ import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.storage.StorageManager;
import android.service.appwidget.AppWidgetServiceDumpProto;
import android.service.appwidget.WidgetProto;
import android.text.TextUtils;
@@ -159,7 +158,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// Bump if the stored widgets need to be upgraded.
private static final int CURRENT_VERSION = 1;
- private static final AtomicLong REQUEST_COUNTER = new AtomicLong();
+ // Every widget update request is associated which an increasing sequence number. This is
+ // used to verify which request has successfully been received by the host.
+ private static final AtomicLong UPDATE_COUNTER = new AtomicLong();
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -814,9 +815,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
Host host = lookupOrAddHostLocked(id);
host.callbacks = callbacks;
+ long updateSequenceNo = UPDATE_COUNTER.incrementAndGet();
int N = appWidgetIds.length;
ArrayList<PendingHostUpdate> outUpdates = new ArrayList<>(N);
-
LongSparseArray<PendingHostUpdate> updatesMap = new LongSparseArray<>();
for (int i = 0; i < N; i++) {
if (host.getPendingUpdatesForId(appWidgetIds[i], updatesMap)) {
@@ -828,6 +829,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
}
+ // Reset the update counter once all the updates have been calculated
+ host.lastWidgetUpdateSequenceNo = updateSequenceNo;
return new ParceledListSlice<>(outUpdates);
}
}
@@ -1914,9 +1917,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// method with a wrong id. In that case, ignore the call.
return;
}
- long requestId = REQUEST_COUNTER.incrementAndGet();
+ long requestId = UPDATE_COUNTER.incrementAndGet();
if (widget != null) {
- widget.updateRequestIds.put(viewId, requestId);
+ widget.updateSequenceNos.put(viewId, requestId);
}
if (widget == null || widget.host == null || widget.host.zombie
|| widget.host.callbacks == null || widget.provider == null
@@ -1941,7 +1944,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
int appWidgetId, int viewId, long requestId) {
try {
callbacks.viewDataChanged(appWidgetId, viewId);
- host.lastWidgetUpdateRequestId = requestId;
+ host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
// It failed; remove the callback. No need to prune because
// we know that this host is still referenced by this instance.
@@ -1988,9 +1991,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
- long requestId = REQUEST_COUNTER.incrementAndGet();
+ long requestId = UPDATE_COUNTER.incrementAndGet();
if (widget != null) {
- widget.updateRequestIds.put(ID_VIEWS_UPDATE, requestId);
+ widget.updateSequenceNos.put(ID_VIEWS_UPDATE, requestId);
}
if (widget == null || widget.provider == null || widget.provider.zombie
|| widget.host.callbacks == null || widget.host.zombie) {
@@ -2013,7 +2016,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
int appWidgetId, RemoteViews views, long requestId) {
try {
callbacks.updateAppWidget(appWidgetId, views);
- host.lastWidgetUpdateRequestId = requestId;
+ host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
synchronized (mLock) {
Slog.e(TAG, "Widget host dead: " + host.id, re);
@@ -2023,11 +2026,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
private void scheduleNotifyProviderChangedLocked(Widget widget) {
- long requestId = REQUEST_COUNTER.incrementAndGet();
+ long requestId = UPDATE_COUNTER.incrementAndGet();
if (widget != null) {
// When the provider changes, reset everything else.
- widget.updateRequestIds.clear();
- widget.updateRequestIds.append(ID_PROVIDER_CHANGED, requestId);
+ widget.updateSequenceNos.clear();
+ widget.updateSequenceNos.append(ID_PROVIDER_CHANGED, requestId);
}
if (widget == null || widget.provider == null || widget.provider.zombie
|| widget.host.callbacks == null || widget.host.zombie) {
@@ -2050,7 +2053,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
int appWidgetId, AppWidgetProviderInfo info, long requestId) {
try {
callbacks.providerChanged(appWidgetId, info);
- host.lastWidgetUpdateRequestId = requestId;
+ host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
synchronized (mLock){
Slog.e(TAG, "Widget host dead: " + host.id, re);
@@ -3887,7 +3890,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
int tag = TAG_UNDEFINED; // for use while saving state (the index)
- long lastWidgetUpdateRequestId; // request id for the last update successfully sent
+ // Sequence no for the last update successfully sent. This is updated whenever a
+ // widget update is successfully sent to the host callbacks. As all new/undelivered updates
+ // will have sequenceNo greater than this, all those updates will be sent when the host
+ // callbacks are attached again.
+ long lastWidgetUpdateSequenceNo;
public int getUserId() {
return UserHandle.getUserId(id.uid);
@@ -3914,18 +3921,18 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
*/
public boolean getPendingUpdatesForId(int appWidgetId,
LongSparseArray<PendingHostUpdate> outUpdates) {
- long updateRequestId = lastWidgetUpdateRequestId;
+ long updateSequenceNo = lastWidgetUpdateSequenceNo;
int N = widgets.size();
for (int i = 0; i < N; i++) {
Widget widget = widgets.get(i);
if (widget.appWidgetId == appWidgetId) {
outUpdates.clear();
- for (int j = widget.updateRequestIds.size() - 1; j >= 0; j--) {
- long requestId = widget.updateRequestIds.valueAt(j);
- if (requestId <= updateRequestId) {
+ for (int j = widget.updateSequenceNos.size() - 1; j >= 0; j--) {
+ long requestId = widget.updateSequenceNos.valueAt(j);
+ if (requestId <= updateSequenceNo) {
continue;
}
- int id = widget.updateRequestIds.keyAt(j);
+ int id = widget.updateSequenceNos.keyAt(j);
final PendingHostUpdate update;
switch (id) {
case ID_PROVIDER_CHANGED:
@@ -4021,8 +4028,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
RemoteViews maskedViews;
Bundle options;
Host host;
- // Request ids for various operations
- SparseLongArray updateRequestIds = new SparseLongArray(2);
+ // Map of request type to updateSequenceNo.
+ SparseLongArray updateSequenceNos = new SparseLongArray(2);
@Override
public String toString() {
diff --git a/com/android/server/audio/PlaybackActivityMonitor.java b/com/android/server/audio/PlaybackActivityMonitor.java
index 6506cf7f..49431733 100644
--- a/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/com/android/server/audio/PlaybackActivityMonitor.java
@@ -184,11 +184,15 @@ public final class PlaybackActivityMonitor
}
}
+ private static final int FLAGS_FOR_SILENCE_OVERRIDE =
+ AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
+ AudioAttributes.FLAG_BYPASS_MUTE;
+
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 &&
+ if ((apc.getAudioAttributes().getAllFlags() & FLAGS_FOR_SILENCE_OVERRIDE)
+ == FLAGS_FOR_SILENCE_OVERRIDE &&
apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_ALARM &&
mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE,
apc.getClientPid(), apc.getClientUid()) ==
diff --git a/com/android/server/autofill/AutofillManagerServiceImpl.java b/com/android/server/autofill/AutofillManagerServiceImpl.java
index 862070ad..880f236c 100644
--- a/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -216,9 +216,12 @@ final class AutofillManagerServiceImpl {
serviceComponent = ComponentName.unflattenFromString(componentName);
serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
0, mUserId);
+ if (serviceInfo == null) {
+ Slog.e(TAG, "Bad AutofillService name: " + componentName);
+ }
} catch (RuntimeException | RemoteException e) {
- Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e);
- return;
+ Slog.e(TAG, "Error getting service info for '" + componentName + "': " + e);
+ serviceInfo = null;
}
}
try {
@@ -228,21 +231,24 @@ final class AutofillManagerServiceImpl {
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) {
- if (!isEnabled) {
- final int sessionCount = mSessions.size();
- for (int i = sessionCount - 1; i >= 0; i--) {
- final Session session = mSessions.valueAt(i);
- session.removeSelfLocked();
- }
+ if (sDebug) {
+ Slog.d(TAG, "Reset component for user " + mUserId + " (" + componentName + ")");
}
- sendStateToClients(false);
}
} catch (Exception e) {
- Slog.e(TAG, "Bad AutofillService '" + componentName + "': " + e);
+ Slog.e(TAG, "Bad AutofillServiceInfo for '" + componentName + "': " + e);
+ mInfo = null;
+ }
+ final boolean isEnabled = isEnabled();
+ if (wasEnabled != isEnabled) {
+ if (!isEnabled) {
+ final int sessionCount = mSessions.size();
+ for (int i = sessionCount - 1; i >= 0; i--) {
+ final Session session = mSessions.valueAt(i);
+ session.removeSelfLocked();
+ }
+ }
+ sendStateToClients(false);
}
}
diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java
index ed00ffed..3c12d670 100644
--- a/com/android/server/autofill/Session.java
+++ b/com/android/server/autofill/Session.java
@@ -495,7 +495,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
notifyUnavailableToClient(false);
}
synchronized (mLock) {
- processResponseLocked(response, requestFlags);
+ processResponseLocked(response, null, requestFlags);
}
final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST, servicePackageName)
@@ -762,13 +762,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
- if (sDebug) Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result);
+ final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
+ if (sDebug) {
+ Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result
+ + ", clientState=" + newClientState);
+ }
if (result instanceof FillResponse) {
writeLog(MetricsEvent.AUTOFILL_AUTHENTICATED);
- replaceResponseLocked(authenticatedResponse, (FillResponse) result);
+ replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState);
} else if (result instanceof Dataset) {
if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
writeLog(MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
+ if (newClientState != null) {
+ if (sDebug) Slog.d(TAG, "Updating client state from auth dataset");
+ mClientState = newClientState;
+ }
final Dataset dataset = (Dataset) result;
authenticatedResponse.getDatasets().set(datasetIdx, dataset);
autoFill(requestId, datasetIdx, dataset, false);
@@ -1491,8 +1499,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
ArraySet<AutofillId> trackedViews = null;
boolean saveOnAllViewsInvisible = false;
+ boolean saveOnFinish = true;
final SaveInfo saveInfo = response.getSaveInfo();
+ final AutofillId saveTriggerId;
if (saveInfo != null) {
+ saveTriggerId = saveInfo.getTriggerId();
+ if (saveTriggerId != null) {
+ writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION);
+ }
saveOnAllViewsInvisible =
(saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
@@ -1509,6 +1523,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Collections.addAll(trackedViews, saveInfo.getOptionalIds());
}
}
+ if ((saveInfo.getFlags() & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) {
+ saveOnFinish = false;
+ }
+
+ } else {
+ saveTriggerId = null;
}
// Must also track that are part of datasets, otherwise the FillUI won't be hidden when
@@ -1533,17 +1553,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
try {
if (sVerbose) {
- Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds);
+ Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds
+ + " (triggering on " + saveTriggerId + ")");
}
mClient.setTrackedViews(id, toArray(trackedViews), saveOnAllViewsInvisible,
- toArray(fillableIds));
+ saveOnFinish, toArray(fillableIds), saveTriggerId);
} catch (RemoteException e) {
Slog.w(TAG, "Cannot set tracked ids", e);
}
}
private void replaceResponseLocked(@NonNull FillResponse oldResponse,
- @NonNull FillResponse newResponse) {
+ @NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
// Disassociate view states with the old response
setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true);
// Move over the id
@@ -1551,7 +1572,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// Replace the old response
mResponses.put(newResponse.getRequestId(), newResponse);
// Now process the new response
- processResponseLocked(newResponse, 0);
+ processResponseLocked(newResponse, newClientState, 0);
}
private void processNullResponseLocked(int flags) {
@@ -1565,7 +1586,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
removeSelf();
}
- private void processResponseLocked(@NonNull FillResponse newResponse, int flags) {
+ private void processResponseLocked(@NonNull FillResponse newResponse,
+ @Nullable Bundle newClientState, int flags) {
// Make sure we are hiding the UI which will be shown
// only if handling the current response requires it.
mUi.hideAll(this);
@@ -1573,14 +1595,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final int requestId = newResponse.getRequestId();
if (sVerbose) {
Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
- + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse);
+ + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse
+ + ",newClientState=" + newClientState);
}
if (mResponses == null) {
mResponses = new SparseArray<>(4);
}
mResponses.put(requestId, newResponse);
- mClientState = newResponse.getClientState();
+ mClientState = newClientState != null ? newClientState : newResponse.getClientState();
setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false);
updateTrackedIdsLocked();
diff --git a/com/android/server/backup/BackupManagerService.java b/com/android/server/backup/BackupManagerService.java
index eabe21fe..f9213aab 100644
--- a/com/android/server/backup/BackupManagerService.java
+++ b/com/android/server/backup/BackupManagerService.java
@@ -319,7 +319,6 @@ public class BackupManagerService implements BackupManagerServiceInterface {
boolean mProvisioned;
boolean mAutoRestore;
PowerManager.WakeLock mWakelock;
- HandlerThread mHandlerThread;
BackupHandler mBackupHandler;
PendingIntent mRunBackupIntent, mRunInitIntent;
BroadcastReceiver mRunBackupReceiver, mRunInitReceiver;
@@ -409,43 +408,37 @@ public class BackupManagerService implements BackupManagerServiceInterface {
// 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(UserHandle.USER_SYSTEM)) {
+ // 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, UserHandle.USER_SYSTEM);
- if (enableState >= 0) {
- if (DEBUG) {
- Slog.i(TAG, "Migrating enable state " + (enableState != 0));
- }
- 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");
- }
+ 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(UserHandle.USER_SYSTEM));
- } 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 {
@@ -1220,7 +1213,7 @@ public class BackupManagerService implements BackupManagerServiceInterface {
// ----- Main service implementation -----
- public BackupManagerService(Context context, Trampoline parent) {
+ public BackupManagerService(Context context, Trampoline parent, HandlerThread backupThread) {
mContext = context;
mPackageManager = context.getPackageManager();
mPackageManagerBinder = AppGlobals.getPackageManager();
@@ -1233,9 +1226,7 @@ public class BackupManagerService implements BackupManagerServiceInterface {
mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
// spin up the backup/restore handler thread
- mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
- mHandlerThread.start();
- mBackupHandler = new BackupHandler(mHandlerThread.getLooper());
+ mBackupHandler = new BackupHandler(backupThread.getLooper());
// Set up our bookkeeping
final ContentResolver resolver = context.getContentResolver();
@@ -1360,7 +1351,7 @@ public class BackupManagerService implements BackupManagerServiceInterface {
if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
- mTransportBoundListener, mHandlerThread.getLooper());
+ mTransportBoundListener, backupThread.getLooper());
mTransportManager.registerAllTransports();
// Now that we know about valid backup participants, parse any
diff --git a/com/android/server/backup/RefactoredBackupManagerService.java b/com/android/server/backup/RefactoredBackupManagerService.java
index f2980659..20f23690 100644
--- a/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/com/android/server/backup/RefactoredBackupManagerService.java
@@ -237,7 +237,6 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
private boolean mProvisioned;
private boolean mAutoRestore;
private PowerManager.WakeLock mWakelock;
- private HandlerThread mHandlerThread;
private BackupHandler mBackupHandler;
private PendingIntent mRunBackupIntent;
private PendingIntent mRunInitIntent;
@@ -556,43 +555,37 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
// 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(UserHandle.USER_SYSTEM)) {
+ // 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, UserHandle.USER_SYSTEM);
- if (enableState >= 0) {
- if (DEBUG) {
- Slog.i(TAG, "Migrating enable state " + (enableState != 0));
- }
- 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");
- }
+ 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(UserHandle.USER_SYSTEM));
- } 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);
}
// Bookkeeping of in-flight operations for timeout etc. purposes. The operation
@@ -729,7 +722,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
// ----- Main service implementation -----
- public RefactoredBackupManagerService(Context context, Trampoline parent) {
+ public RefactoredBackupManagerService(Context context, Trampoline parent,
+ HandlerThread backupThread) {
mContext = context;
mPackageManager = context.getPackageManager();
mPackageManagerBinder = AppGlobals.getPackageManager();
@@ -742,9 +736,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
// spin up the backup/restore handler thread
- mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
- mHandlerThread.start();
- mBackupHandler = new BackupHandler(this, mHandlerThread.getLooper());
+ mBackupHandler = new BackupHandler(this, backupThread.getLooper());
// Set up our bookkeeping
final ContentResolver resolver = context.getContentResolver();
@@ -824,7 +816,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
- mTransportBoundListener, mHandlerThread.getLooper());
+ mTransportBoundListener, backupThread.getLooper());
mTransportManager.registerAllTransports();
// Now that we know about valid backup participants, parse any
diff --git a/com/android/server/backup/Trampoline.java b/com/android/server/backup/Trampoline.java
index 9739e380..9847edf8 100644
--- a/com/android/server/backup/Trampoline.java
+++ b/com/android/server/backup/Trampoline.java
@@ -28,11 +28,15 @@ import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
@@ -75,6 +79,8 @@ public class Trampoline extends IBackupManager.Stub {
final boolean mGlobalDisable;
volatile BackupManagerServiceInterface mService;
+ private HandlerThread mHandlerThread;
+
public Trampoline(Context context) {
mContext = context;
mGlobalDisable = isBackupDisabled();
@@ -111,11 +117,11 @@ public class Trampoline extends IBackupManager.Stub {
}
protected BackupManagerServiceInterface createRefactoredBackupManagerService() {
- return new RefactoredBackupManagerService(mContext, this);
+ return new RefactoredBackupManagerService(mContext, this, mHandlerThread);
}
protected BackupManagerServiceInterface createBackupManagerService() {
- return new BackupManagerService(mContext, this);
+ return new BackupManagerService(mContext, this, mHandlerThread);
}
// internal control API
@@ -140,10 +146,21 @@ public class Trampoline extends IBackupManager.Stub {
}
void unlockSystemUser() {
- BackupManagerServiceInterface svc = mService;
- if (svc != null) {
- svc.unlockSystemUser();
- }
+ mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
+ mHandlerThread.start();
+
+ Handler h = new Handler(mHandlerThread.getLooper());
+ h.post(() -> {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
+ initialize(UserHandle.USER_SYSTEM);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ BackupManagerServiceInterface svc = mService;
+ Slog.i(TAG, "Unlocking system user; mService=" + mService);
+ if (svc != null) {
+ svc.unlockSystemUser();
+ }
+ });
}
public void setBackupServiceActive(final int userHandle, boolean makeActive) {
diff --git a/com/android/server/clipboard/ClipboardService.java b/com/android/server/clipboard/ClipboardService.java
index e6228d46..0c9d70a9 100644
--- a/com/android/server/clipboard/ClipboardService.java
+++ b/com/android/server/clipboard/ClipboardService.java
@@ -435,11 +435,12 @@ public class ClipboardService extends SystemService {
}
private boolean isDeviceLocked() {
+ int callingUserId = UserHandle.getCallingUserId();
final long token = Binder.clearCallingIdentity();
try {
final KeyguardManager keyguardManager = getContext().getSystemService(
KeyguardManager.class);
- return keyguardManager != null && keyguardManager.isDeviceLocked();
+ return keyguardManager != null && keyguardManager.isDeviceLocked(callingUserId);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/com/android/server/connectivity/Tethering.java b/com/android/server/connectivity/Tethering.java
index 5583e86c..d7cd81ff 100644
--- a/com/android/server/connectivity/Tethering.java
+++ b/com/android/server/connectivity/Tethering.java
@@ -1371,6 +1371,7 @@ public class Tethering extends BaseNetworkObserver {
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
}
}
+ mUpstreamNetworkMonitor.setCurrentUpstream((ns != null) ? ns.network : null);
setUpstreamNetwork(ns);
}
diff --git a/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index c5f75280..b35ed751 100644
--- a/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -95,7 +95,10 @@ public class UpstreamNetworkMonitor {
private NetworkCallback mDefaultNetworkCallback;
private NetworkCallback mMobileNetworkCallback;
private boolean mDunRequired;
- private Network mCurrentDefault;
+ // The current system default network (not really used yet).
+ private Network mDefaultInternetNetwork;
+ // The current upstream network used for tethering.
+ private Network mTetheringUpstreamNetwork;
public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) {
mContext = ctx;
@@ -130,10 +133,12 @@ public class UpstreamNetworkMonitor {
releaseCallback(mDefaultNetworkCallback);
mDefaultNetworkCallback = null;
+ mDefaultInternetNetwork = null;
releaseCallback(mListenAllCallback);
mListenAllCallback = null;
+ mTetheringUpstreamNetwork = null;
mNetworkMap.clear();
}
@@ -207,7 +212,7 @@ public class UpstreamNetworkMonitor {
break;
default:
/* If we've found an active upstream connection that's not DUN/HIPRI
- * we should stop any outstanding DUN/HIPRI start requests.
+ * we should stop any outstanding DUN/HIPRI requests.
*
* If we found NONE we don't want to do this as we want any previous
* requests to keep trying to bring up something we can use.
@@ -219,6 +224,10 @@ public class UpstreamNetworkMonitor {
return typeStatePair.ns;
}
+ public void setCurrentUpstream(Network upstream) {
+ mTetheringUpstreamNetwork = upstream;
+ }
+
public Set<IpPrefix> getLocalPrefixes() {
return (Set<IpPrefix>) mLocalPrefixes.clone();
}
@@ -250,7 +259,7 @@ public class UpstreamNetworkMonitor {
// These request*() calls can be deleted post oag/339444.
return;
}
- mCurrentDefault = network;
+ mDefaultInternetNetwork = network;
break;
case CALLBACK_MOBILE_REQUEST:
@@ -302,6 +311,13 @@ public class UpstreamNetworkMonitor {
network, newNc));
}
+ // Log changes in upstream network signal strength, if available.
+ if (network.equals(mTetheringUpstreamNetwork) && newNc.hasSignalStrength()) {
+ final int newSignal = newNc.getSignalStrength();
+ final String prevSignal = getSignalStrength(prev.networkCapabilities);
+ mLog.logf("upstream network signal strength: %s -> %s", prevSignal, newSignal);
+ }
+
mNetworkMap.put(network, new NetworkState(
null, prev.linkProperties, newNc, network, null, null));
// TODO: If sufficient information is available to select a more
@@ -330,9 +346,21 @@ public class UpstreamNetworkMonitor {
notifyTarget(EVENT_ON_LINKPROPERTIES, network);
}
+ private void handleSuspended(int callbackType, Network network) {
+ if (callbackType != CALLBACK_LISTEN_ALL) return;
+ if (!network.equals(mTetheringUpstreamNetwork)) return;
+ mLog.log("SUSPENDED current upstream: " + network);
+ }
+
+ private void handleResumed(int callbackType, Network network) {
+ if (callbackType != CALLBACK_LISTEN_ALL) return;
+ if (!network.equals(mTetheringUpstreamNetwork)) return;
+ mLog.log("RESUMED current upstream: " + network);
+ }
+
private void handleLost(int callbackType, Network network) {
if (callbackType == CALLBACK_TRACK_DEFAULT) {
- mCurrentDefault = null;
+ mDefaultInternetNetwork = null;
// Receiving onLost() for a default network does not necessarily
// mean the network is gone. We wait for a separate notification
// on either the LISTEN_ALL or MOBILE_REQUEST callbacks before
@@ -401,8 +429,15 @@ public class UpstreamNetworkMonitor {
recomputeLocalPrefixes();
}
- // TODO: Handle onNetworkSuspended();
- // TODO: Handle onNetworkResumed();
+ @Override
+ public void onNetworkSuspended(Network network) {
+ handleSuspended(mCallbackType, network);
+ }
+
+ @Override
+ public void onNetworkResumed(Network network) {
+ handleResumed(mCallbackType, network);
+ }
@Override
public void onLost(Network network) {
@@ -467,4 +502,9 @@ public class UpstreamNetworkMonitor {
return prefixSet;
}
+
+ private static String getSignalStrength(NetworkCapabilities nc) {
+ if (nc == null || !nc.hasSignalStrength()) return "unknown";
+ return Integer.toString(nc.getSignalStrength());
+ }
}
diff --git a/com/android/server/display/AutomaticBrightnessController.java b/com/android/server/display/AutomaticBrightnessController.java
index 9aabdab7..9a6e6094 100644
--- a/com/android/server/display/AutomaticBrightnessController.java
+++ b/com/android/server/display/AutomaticBrightnessController.java
@@ -29,6 +29,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
+import android.os.Trace;
import android.text.format.DateUtils;
import android.util.EventLog;
import android.util.MathUtils;
@@ -304,6 +305,7 @@ class AutomaticBrightnessController {
}
private void handleLightSensorEvent(long time, float lux) {
+ Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux);
mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
if (mAmbientLightRingBuffer.size() == 0) {
diff --git a/com/android/server/display/DisplayPowerController.java b/com/android/server/display/DisplayPowerController.java
index f8e58362..f930b523 100644
--- a/com/android/server/display/DisplayPowerController.java
+++ b/com/android/server/display/DisplayPowerController.java
@@ -683,8 +683,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// Configure auto-brightness.
boolean autoBrightnessEnabled = false;
if (mAutomaticBrightnessController != null) {
- final boolean autoBrightnessEnabledInDoze = mAllowAutoBrightnessWhileDozingConfig
- && (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND);
+ final boolean autoBrightnessEnabledInDoze =
+ mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
autoBrightnessEnabled = mPowerRequest.useAutoBrightness
&& (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& brightness < 0;
@@ -726,8 +726,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
// Use default brightness when dozing unless overridden.
- if (brightness < 0 && (state == Display.STATE_DOZE
- || state == Display.STATE_DOZE_SUSPEND)) {
+ if (brightness < 0 && Display.isDozeState(state)) {
brightness = mScreenBrightnessDozeConfig;
}
@@ -777,7 +776,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// Skip the animation when the screen is off or suspended or transition to/from VR.
if (!mPendingScreenOff) {
if (mSkipScreenOnBrightnessRamp) {
-
if (state == Display.STATE_ON) {
if (mSkipRampState == RAMP_STATE_SKIP_NONE && mDozing) {
mInitialAutoBrightness = brightness;
@@ -794,15 +792,25 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
- boolean wasOrWillBeInVr = (state == Display.STATE_VR || oldState == Display.STATE_VR);
- if ((state == Display.STATE_ON
- && mSkipRampState == RAMP_STATE_SKIP_NONE
- || state == Display.STATE_DOZE && !mBrightnessBucketsInDozeConfig)
- && !wasOrWillBeInVr) {
+ final boolean wasOrWillBeInVr =
+ (state == Display.STATE_VR || oldState == Display.STATE_VR);
+ final boolean initialRampSkip =
+ state == Display.STATE_ON && mSkipRampState != RAMP_STATE_SKIP_NONE;
+ // While dozing, sometimes the brightness is split into buckets. Rather than animating
+ // through the buckets, which is unlikely to be smooth in the first place, just jump
+ // right to the suggested brightness.
+ final boolean hasBrightnessBuckets =
+ Display.isDozeState(state) && mBrightnessBucketsInDozeConfig;
+ // If the color fade is totally covering the screen then we can change the backlight
+ // level without it being a noticeable jump since any actual content isn't yet visible.
+ final boolean isDisplayContentVisible =
+ mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
+ if (initialRampSkip || hasBrightnessBuckets
+ || wasOrWillBeInVr || !isDisplayContentVisible) {
+ animateScreenBrightness(brightness, 0);
+ } else {
animateScreenBrightness(brightness,
slowChange ? mBrightnessRampRateSlow : mBrightnessRampRateFast);
- } else {
- animateScreenBrightness(brightness, 0);
}
}
@@ -925,6 +933,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
if (!reportOnly) {
+ Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
mPowerState.setScreenState(state);
// Tell battery stats about the transition.
try {
diff --git a/com/android/server/job/controllers/TimeController.java b/com/android/server/job/controllers/TimeController.java
index d90699a6..ee4c606f 100644
--- a/com/android/server/job/controllers/TimeController.java
+++ b/com/android/server/job/controllers/TimeController.java
@@ -110,7 +110,7 @@ public final class TimeController extends StateController {
maybeUpdateAlarmsLocked(
job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
- job.getSourceUid());
+ new WorkSource(job.getSourceUid(), job.getSourcePackageName()));
}
}
@@ -156,6 +156,7 @@ public final class TimeController extends StateController {
synchronized (mLock) {
long nextExpiryTime = Long.MAX_VALUE;
int nextExpiryUid = 0;
+ String nextExpiryPackageName = null;
final long nowElapsedMillis = SystemClock.elapsedRealtime();
Iterator<JobStatus> it = mTrackedJobs.iterator();
@@ -171,10 +172,13 @@ public final class TimeController extends StateController {
} else { // Sorted by expiry time, so take the next one and stop.
nextExpiryTime = job.getLatestRunTimeElapsed();
nextExpiryUid = job.getSourceUid();
+ nextExpiryPackageName = job.getSourcePackageName();
break;
}
}
- setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryUid);
+ setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryPackageName != null
+ ? new WorkSource(nextExpiryUid, nextExpiryPackageName)
+ : new WorkSource(nextExpiryUid));
}
}
@@ -200,6 +204,7 @@ public final class TimeController extends StateController {
final long nowElapsedMillis = SystemClock.elapsedRealtime();
long nextDelayTime = Long.MAX_VALUE;
int nextDelayUid = 0;
+ String nextDelayPackageName = null;
boolean ready = false;
Iterator<JobStatus> it = mTrackedJobs.iterator();
while (it.hasNext()) {
@@ -221,13 +226,16 @@ public final class TimeController extends StateController {
if (nextDelayTime > jobDelayTime) {
nextDelayTime = jobDelayTime;
nextDelayUid = job.getSourceUid();
+ nextDelayPackageName = job.getSourcePackageName();
}
}
}
if (ready) {
mStateChangedListener.onControllerStateChanged();
}
- setDelayExpiredAlarmLocked(nextDelayTime, nextDelayUid);
+ setDelayExpiredAlarmLocked(nextDelayTime, nextDelayPackageName != null
+ ? new WorkSource(nextDelayUid, nextDelayPackageName)
+ : new WorkSource(nextDelayUid));
}
}
@@ -241,12 +249,12 @@ public final class TimeController extends StateController {
}
private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed,
- int uid) {
+ WorkSource ws) {
if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
- setDelayExpiredAlarmLocked(delayExpiredElapsed, uid);
+ setDelayExpiredAlarmLocked(delayExpiredElapsed, ws);
}
if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) {
- setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, uid);
+ setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, ws);
}
}
@@ -255,11 +263,11 @@ public final class TimeController extends StateController {
* delay will expire.
* This alarm <b>will</b> wake up the phone.
*/
- private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) {
+ private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener,
- mNextDelayExpiredElapsedMillis, uid);
+ mNextDelayExpiredElapsedMillis, ws);
}
/**
@@ -267,11 +275,11 @@ public final class TimeController extends StateController {
* deadline will expire.
* This alarm <b>will</b> wake up the phone.
*/
- private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) {
+ private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener,
- mNextJobExpiredElapsedMillis, uid);
+ mNextJobExpiredElapsedMillis, ws);
}
private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
@@ -283,7 +291,7 @@ public final class TimeController extends StateController {
}
private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener,
- long alarmTimeElapsed, int uid) {
+ long alarmTimeElapsed, WorkSource ws) {
ensureAlarmServiceLocked();
if (alarmTimeElapsed == Long.MAX_VALUE) {
mAlarmService.cancel(listener);
@@ -292,7 +300,7 @@ public final class TimeController extends StateController {
Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed);
}
mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed,
- AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, new WorkSource(uid));
+ AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, ws);
}
}
diff --git a/com/android/server/location/GnssLocationProvider.java b/com/android/server/location/GnssLocationProvider.java
index 0aa6a90e..e41c17df 100644
--- a/com/android/server/location/GnssLocationProvider.java
+++ b/com/android/server/location/GnssLocationProvider.java
@@ -417,7 +417,11 @@ public class GnssLocationProvider implements LocationProviderInterface {
// stops output right at 600m/s, depriving this of the information of a device that reaches
// greater than 600m/s, and higher than the speed of sound to avoid impacting most use cases.
private static final float ITAR_SPEED_LIMIT_METERS_PER_SECOND = 400.0F;
- private boolean mItarSpeedLimitExceeded = false;
+
+ // TODO: improve comment
+ // Volatile to ensure that potentially near-concurrent outputs from HAL
+ // react to this value change promptly
+ private volatile boolean mItarSpeedLimitExceeded = false;
// GNSS Metrics
private GnssMetrics mGnssMetrics;
diff --git a/com/android/server/media/MediaSessionRecord.java b/com/android/server/media/MediaSessionRecord.java
index 0b11479a..664d2f97 100644
--- a/com/android/server/media/MediaSessionRecord.java
+++ b/com/android/server/media/MediaSessionRecord.java
@@ -784,6 +784,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
mService.enforcePhoneStatePermission(pid, uid);
}
mFlags = flags;
+ if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mService.setGlobalPrioritySession(MediaSessionRecord.this);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
}
diff --git a/com/android/server/media/MediaSessionService.java b/com/android/server/media/MediaSessionService.java
index b9a2d184..aa652445 100644
--- a/com/android/server/media/MediaSessionService.java
+++ b/com/android/server/media/MediaSessionService.java
@@ -178,17 +178,6 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
- if (mGlobalPrioritySession != record) {
- Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
- + " to " + record);
- mGlobalPrioritySession = record;
- if (user != null && user.mPriorityStack.contains(record)) {
- // Handle the global priority session separately.
- // Otherwise, it will be the media button session even after it becomes
- // inactive because it has been the lastly played media app.
- user.mPriorityStack.removeSession(record);
- }
- }
if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
}
@@ -204,10 +193,27 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
+ public void setGlobalPrioritySession(MediaSessionRecord record) {
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+ if (mGlobalPrioritySession != record) {
+ Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
+ + " to " + record);
+ mGlobalPrioritySession = record;
+ if (user != null && user.mPriorityStack.contains(record)) {
+ // Handle the global priority session separately.
+ // Otherwise, it can be the media button session regardless of the active state
+ // because it or other system components might have been the lastly played media
+ // app.
+ user.mPriorityStack.removeSession(record);
+ }
+ }
+ }
+ }
+
private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
- List<MediaSessionRecord> records;
+ List<MediaSessionRecord> records = new ArrayList<>();
if (userId == UserHandle.USER_ALL) {
- records = new ArrayList<>();
int size = mUserRecords.size();
for (int i = 0; i < size; i++) {
records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
@@ -216,9 +222,9 @@ public class MediaSessionService extends SystemService implements Monitor {
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null) {
Log.w(TAG, "getSessions failed. Unknown user " + userId);
- return new ArrayList<>();
+ return records;
}
- records = user.mPriorityStack.getActiveSessions(userId);
+ records.addAll(user.mPriorityStack.getActiveSessions(userId));
}
// Return global priority session at the first whenever it's asked.
diff --git a/com/android/server/notification/NotificationManagerService.java b/com/android/server/notification/NotificationManagerService.java
index 14cd0555..238d87b7 100644
--- a/com/android/server/notification/NotificationManagerService.java
+++ b/com/android/server/notification/NotificationManagerService.java
@@ -133,7 +133,6 @@ 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;
@@ -282,6 +281,7 @@ public class NotificationManagerService extends SystemService {
private WindowManagerInternal mWindowManagerInternal;
private AlarmManager mAlarmManager;
private ICompanionDeviceManager mCompanionManager;
+ private AccessibilityManager mAccessibilityManager;
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
@@ -1221,6 +1221,12 @@ public class NotificationManagerService extends SystemService {
mUsageStats = us;
}
+ @VisibleForTesting
+ void setAccessibilityManager(AccessibilityManager am) {
+ mAccessibilityManager = am;
+ }
+
+
// TODO: All tests should use this init instead of the one-off setters above.
@VisibleForTesting
void init(Looper looper, IPackageManager packageManager,
@@ -1235,6 +1241,8 @@ public class NotificationManagerService extends SystemService {
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE);
+ mAccessibilityManager =
+ (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
mAm = ActivityManager.getService();
mPackageManager = packageManager;
mPackageManagerClient = packageManagerClient;
@@ -3254,7 +3262,7 @@ public class NotificationManagerService extends SystemService {
final NotificationRecord nr = mNotificationList.get(i);
if (filter.filtered && !filter.matches(nr.sbn)) continue;
nr.dump(proto, filter.redact);
- proto.write(NotificationRecordProto.STATE, NotificationServiceProto.POSTED);
+ proto.write(NotificationRecordProto.STATE, NotificationRecordProto.POSTED);
}
}
N = mEnqueuedNotifications.size();
@@ -3263,7 +3271,7 @@ public class NotificationManagerService extends SystemService {
final NotificationRecord nr = mEnqueuedNotifications.get(i);
if (filter.filtered && !filter.matches(nr.sbn)) continue;
nr.dump(proto, filter.redact);
- proto.write(NotificationRecordProto.STATE, NotificationServiceProto.ENQUEUED);
+ proto.write(NotificationRecordProto.STATE, NotificationRecordProto.ENQUEUED);
}
}
List<NotificationRecord> snoozed = mSnoozeHelper.getSnoozed();
@@ -3273,7 +3281,7 @@ public class NotificationManagerService extends SystemService {
final NotificationRecord nr = snoozed.get(i);
if (filter.filtered && !filter.matches(nr.sbn)) continue;
nr.dump(proto, filter.redact);
- proto.write(NotificationRecordProto.STATE, NotificationServiceProto.SNOOZED);
+ proto.write(NotificationRecordProto.STATE, NotificationRecordProto.SNOOZED);
}
}
proto.end(records);
@@ -4117,13 +4125,16 @@ public class NotificationManagerService extends SystemService {
// These are set inside the conditional if the notification is allowed to make noise.
boolean hasValidVibrate = false;
boolean hasValidSound = false;
+ boolean sentAccessibilityEvent = false;
+ // If the notification will appear in the status bar, it should send an accessibility
+ // event
+ if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
+ sendAccessibilityEvent(notification, record.sbn.getPackageName());
+ sentAccessibilityEvent = true;
+ }
if (aboveThreshold && isNotificationForCurrentUser(record)) {
- // If the notification will appear in the status bar, it should send an accessibility
- // event
- if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
- sendAccessibilityEvent(notification, record.sbn.getPackageName());
- }
+
if (mSystemReady && mAudioManager != null) {
Uri soundUri = record.getSound();
hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
@@ -4141,6 +4152,10 @@ public class NotificationManagerService extends SystemService {
boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
+ if (!sentAccessibilityEvent) {
+ sendAccessibilityEvent(notification, record.sbn.getPackageName());
+ sentAccessibilityEvent = true;
+ }
if (DBG) Slog.v(TAG, "Interrupting!");
if (hasValidSound) {
mSoundNotificationKey = key;
@@ -4661,8 +4676,7 @@ public class NotificationManagerService extends SystemService {
}
void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
- AccessibilityManager manager = AccessibilityManager.getInstance(getContext());
- if (!manager.isEnabled()) {
+ if (!mAccessibilityManager.isEnabled()) {
return;
}
@@ -4676,7 +4690,7 @@ public class NotificationManagerService extends SystemService {
event.getText().add(tickerText);
}
- manager.sendAccessibilityEvent(event);
+ mAccessibilityManager.sendAccessibilityEvent(event);
}
/**
diff --git a/com/android/server/notification/ZenModeFiltering.java b/com/android/server/notification/ZenModeFiltering.java
index a7a2743d..abf29006 100644
--- a/com/android/server/notification/ZenModeFiltering.java
+++ b/com/android/server/notification/ZenModeFiltering.java
@@ -117,15 +117,19 @@ public class ZenModeFiltering {
ZenLog.traceIntercepted(record, "alarmsOnly");
return true;
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- if (isAlarm(record)) {
- // Alarms are always priority
- return false;
- }
// allow user-prioritized packages through in priority mode
if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
ZenLog.traceNotIntercepted(record, "priorityApp");
return false;
}
+
+ if (isAlarm(record)) {
+ if (!config.allowAlarms) {
+ ZenLog.traceIntercepted(record, "!allowAlarms");
+ return true;
+ }
+ return false;
+ }
if (isCall(record)) {
if (config.allowRepeatCallers
&& REPEAT_CALLERS.isRepeat(mContext, extras(record))) {
@@ -159,6 +163,15 @@ public class ZenModeFiltering {
}
return false;
}
+ AudioAttributes aa = record.getAudioAttributes();
+ if (aa != null && AudioAttributes.SUPPRESSIBLE_USAGES.get(aa.getUsage()) ==
+ AudioAttributes.SUPPRESSIBLE_MEDIA_SYSTEM_OTHER) {
+ if (!config.allowMediaSystemOther) {
+ ZenLog.traceIntercepted(record, "!allowMediaSystemOther");
+ return true;
+ }
+ return false;
+ }
ZenLog.traceIntercepted(record, "!priority");
return true;
default:
diff --git a/com/android/server/notification/ZenModeHelper.java b/com/android/server/notification/ZenModeHelper.java
index 9fcc67df..710684f4 100644
--- a/com/android/server/notification/ZenModeHelper.java
+++ b/com/android/server/notification/ZenModeHelper.java
@@ -59,6 +59,7 @@ import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.server.LocalServices;
@@ -87,7 +88,7 @@ public class ZenModeHelper {
private final Context mContext;
private final H mHandler;
private final SettingsObserver mSettingsObserver;
- private final AppOpsManager mAppOps;
+ @VisibleForTesting protected final AppOpsManager mAppOps;
protected ZenModeConfig mDefaultConfig;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final ZenModeFiltering mFiltering;
@@ -102,9 +103,9 @@ public class ZenModeHelper {
private final String SCHEDULED_DEFAULT_RULE_1 = "SCHEDULED_DEFAULT_RULE_1";
private final String SCHEDULED_DEFAULT_RULE_2 = "SCHEDULED_DEFAULT_RULE_2";
- private int mZenMode;
+ @VisibleForTesting protected int mZenMode;
private int mUser = UserHandle.USER_SYSTEM;
- protected ZenModeConfig mConfig;
+ @VisibleForTesting protected ZenModeConfig mConfig;
private AudioManagerInternal mAudioManager;
protected PackageManager mPm;
private long mSuppressedEffects;
@@ -595,8 +596,9 @@ public class ZenModeHelper {
pw.println(config);
return;
}
- pw.printf("allow(calls=%b,callsFrom=%s,repeatCallers=%b,messages=%b,messagesFrom=%s,"
+ pw.printf("allow(alarms=%b,media=%bcalls=%b,callsFrom=%s,repeatCallers=%b,messages=%b,messagesFrom=%s,"
+ "events=%b,reminders=%b,whenScreenOff=%b,whenScreenOn=%b)\n",
+ config.allowAlarms, config.allowMediaSystemOther,
config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom),
config.allowRepeatCallers, config.allowMessages,
ZenModeConfig.sourceToString(config.allowMessagesFrom),
@@ -813,7 +815,8 @@ public class ZenModeHelper {
}
}
- private void applyRestrictions() {
+ @VisibleForTesting
+ protected void applyRestrictions() {
final boolean zen = mZenMode != Global.ZEN_MODE_OFF;
// notification restrictions
@@ -822,6 +825,10 @@ public class ZenModeHelper {
// call restrictions
final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers
|| (mSuppressedEffects & SUPPRESSED_EFFECT_CALLS) != 0;
+ // alarm restrictions
+ final boolean muteAlarms = zen && !mConfig.allowAlarms;
+ // alarm restrictions
+ final boolean muteMediaAndSystemSounds = zen && !mConfig.allowMediaSystemOther;
// total silence restrictions
final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
@@ -833,13 +840,18 @@ public class ZenModeHelper {
applyRestrictions(muteNotifications || muteEverything, usage);
} else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_CALL) {
applyRestrictions(muteCalls || muteEverything, usage);
+ } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_ALARM) {
+ applyRestrictions(muteAlarms || muteEverything, usage);
+ } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_MEDIA_SYSTEM_OTHER) {
+ applyRestrictions(muteMediaAndSystemSounds || muteEverything, usage);
} else {
applyRestrictions(muteEverything, usage);
}
}
}
- private void applyRestrictions(boolean mute, int usage) {
+ @VisibleForTesting
+ protected void applyRestrictions(boolean mute, int usage) {
final String[] exceptionPackages = null; // none (for now)
mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, usage,
mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
diff --git a/com/android/server/pm/LauncherAppsService.java b/com/android/server/pm/LauncherAppsService.java
index 4a5ce120..b06b5838 100644
--- a/com/android/server/pm/LauncherAppsService.java
+++ b/com/android/server/pm/LauncherAppsService.java
@@ -30,12 +30,10 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ILauncherApps;
import android.content.pm.IOnAppsChangedListener;
-import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps.ShortcutQuery;
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.ResolveInfo;
import android.content.pm.ShortcutInfo;
@@ -64,7 +62,6 @@ import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -89,10 +86,14 @@ public class LauncherAppsService extends SystemService {
static class BroadcastCookie {
public final UserHandle user;
public final String packageName;
+ public final int callingUid;
+ public final int callingPid;
- BroadcastCookie(UserHandle userHandle, String packageName) {
+ BroadcastCookie(UserHandle userHandle, String packageName, int callingPid, int callingUid) {
this.user = userHandle;
this.packageName = packageName;
+ this.callingUid = callingUid;
+ this.callingPid = callingPid;
}
}
@@ -127,6 +128,11 @@ public class LauncherAppsService extends SystemService {
return getCallingUid();
}
+ @VisibleForTesting
+ int injectBinderCallingPid() {
+ return getCallingPid();
+ }
+
final int injectCallingUserId() {
return UserHandle.getUserId(injectBinderCallingUid());
}
@@ -166,7 +172,7 @@ public class LauncherAppsService extends SystemService {
}
mListeners.unregister(listener);
mListeners.register(listener, new BroadcastCookie(UserHandle.of(getCallingUserId()),
- callingPackage));
+ callingPackage, injectBinderCallingPid(), injectBinderCallingUid()));
}
}
@@ -438,7 +444,7 @@ public class LauncherAppsService extends SystemService {
private void ensureShortcutPermission(@NonNull String callingPackage) {
verifyCallingPackage(callingPackage);
if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
- callingPackage)) {
+ callingPackage, injectBinderCallingPid(), injectBinderCallingUid())) {
throw new SecurityException("Caller can't access shortcut information");
}
}
@@ -461,7 +467,8 @@ public class LauncherAppsService extends SystemService {
return new ParceledListSlice<>((List<ShortcutInfo>)
mShortcutServiceInternal.getShortcuts(getCallingUserId(),
callingPackage, changedSince, packageName, shortcutIds,
- componentName, flags, targetUser.getIdentifier()));
+ componentName, flags, targetUser.getIdentifier(),
+ injectBinderCallingPid(), injectBinderCallingUid()));
}
@Override
@@ -514,7 +521,7 @@ public class LauncherAppsService extends SystemService {
public boolean hasShortcutHostPermission(String callingPackage) {
verifyCallingPackage(callingPackage);
return mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
- callingPackage);
+ callingPackage, injectBinderCallingPid(), injectBinderCallingUid());
}
@Override
@@ -536,7 +543,8 @@ public class LauncherAppsService extends SystemService {
}
final Intent[] intents = mShortcutServiceInternal.createShortcutIntents(
- getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId);
+ getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId,
+ injectBinderCallingPid(), injectBinderCallingUid());
if (intents == null || intents.length == 0) {
return false;
}
@@ -901,7 +909,8 @@ public class LauncherAppsService extends SystemService {
// Make sure the caller has the permission.
if (!mShortcutServiceInternal.hasShortcutHostPermission(
- launcherUserId, cookie.packageName)) {
+ launcherUserId, cookie.packageName,
+ cookie.callingPid, cookie.callingUid)) {
continue;
}
// Each launcher has a different set of pinned shortcuts, so we need to do a
@@ -914,8 +923,8 @@ public class LauncherAppsService extends SystemService {
/* changedSince= */ 0, packageName, /* shortcutIds=*/ null,
/* component= */ null,
ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY
- | ShortcutQuery.FLAG_GET_ALL_KINDS
- , userId);
+ | ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED
+ , userId, cookie.callingPid, cookie.callingUid);
try {
listener.onShortcutChanged(user, packageName,
new ParceledListSlice<>(list));
diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java
index 8ebeeae2..cf0ffbb1 100644
--- a/com/android/server/pm/PackageDexOptimizer.java
+++ b/com/android/server/pm/PackageDexOptimizer.java
@@ -236,9 +236,10 @@ public class PackageDexOptimizer {
*/
@GuardedBy("mInstallLock")
private int dexOptPath(PackageParser.Package pkg, String path, String isa,
- String compilerFilter, boolean profileUpdated, String sharedLibrariesPath,
+ String compilerFilter, boolean profileUpdated, String classLoaderContext,
int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade) {
- int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, profileUpdated, downgrade);
+ int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext,
+ profileUpdated, downgrade);
if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
return DEX_OPT_SKIPPED;
}
@@ -251,8 +252,8 @@ public class PackageDexOptimizer {
Log.i(TAG, "Running dexopt (dexoptNeeded=" + dexoptNeeded + ") on: " + path
+ " pkg=" + pkg.applicationInfo.packageName + " isa=" + isa
+ " dexoptFlags=" + printDexoptFlags(dexoptFlags)
- + " target-filter=" + compilerFilter + " oatDir=" + oatDir
- + " sharedLibraries=" + sharedLibrariesPath);
+ + " targetFilter=" + compilerFilter + " oatDir=" + oatDir
+ + " classLoaderContext=" + classLoaderContext);
try {
long startTime = System.currentTimeMillis();
@@ -261,7 +262,7 @@ public class PackageDexOptimizer {
// installd only uses downgrade flag for secondary dex files and ignores it for
// primary dex files.
mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
- compilerFilter, pkg.volumeUuid, sharedLibrariesPath, pkg.applicationInfo.seInfo,
+ compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo,
false /* downgrade*/);
if (packageStats != null) {
@@ -508,11 +509,11 @@ public class PackageDexOptimizer {
* configuration (isa, compiler filter, profile).
*/
private int getDexoptNeeded(String path, String isa, String compilerFilter,
- boolean newProfile, boolean downgrade) {
+ String classLoaderContext, boolean newProfile, boolean downgrade) {
int dexoptNeeded;
try {
- dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, newProfile,
- downgrade);
+ dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, classLoaderContext,
+ newProfile, downgrade);
} catch (IOException ioe) {
Slog.w(TAG, "IOException reading apk: " + path, ioe);
return DEX_OPT_FAILED;
diff --git a/com/android/server/pm/PackageInstallerSession.java b/com/android/server/pm/PackageInstallerSession.java
index d62f0934..496ebc23 100644
--- a/com/android/server/pm/PackageInstallerSession.java
+++ b/com/android/server/pm/PackageInstallerSession.java
@@ -1127,15 +1127,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mResolvedInstructionSets.add(archSubDir.getName());
List<File> oatFiles = Arrays.asList(archSubDir.listFiles());
-
- // Only add compiled files associated with the base.
- // Once b/62269291 is resolved, we can add all compiled files again.
- for (File oatFile : oatFiles) {
- if (oatFile.getName().equals("base.art")
- || oatFile.getName().equals("base.odex")
- || oatFile.getName().equals("base.vdex")) {
- mResolvedInheritedFiles.add(oatFile);
- }
+ if (!oatFiles.isEmpty()) {
+ mResolvedInheritedFiles.addAll(oatFiles);
}
}
}
diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java
index 7d1a6470..275db1fa 100644
--- a/com/android/server/pm/PackageManagerService.java
+++ b/com/android/server/pm/PackageManagerService.java
@@ -398,7 +398,7 @@ public class PackageManagerService extends IPackageManager.Stub
static final boolean DEBUG_DOMAIN_VERIFICATION = false;
private static final boolean DEBUG_BACKUP = false;
private static final boolean DEBUG_INSTALL = false;
- private static final boolean DEBUG_REMOVE = false;
+ public static final boolean DEBUG_REMOVE = false;
private static final boolean DEBUG_BROADCASTS = false;
private static final boolean DEBUG_SHOW_INFO = false;
private static final boolean DEBUG_PACKAGE_INFO = false;
@@ -406,7 +406,7 @@ public class PackageManagerService extends IPackageManager.Stub
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;
+ public static final boolean DEBUG_PERMISSIONS = false;
private static final boolean DEBUG_SHARED_LIBRARIES = false;
private static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
@@ -954,9 +954,6 @@ public class PackageManagerService extends IPackageManager.Stub
final SparseArray<PackageVerificationState> mPendingVerification
= new SparseArray<PackageVerificationState>();
- /** Set of packages associated with each app op permission. */
- final ArrayMap<String, ArraySet<String>> mAppOpPermissionPackages = new ArrayMap<>();
-
final PackageInstallerService mInstallerService;
private final PackageDexOptimizer mPackageDexOptimizer;
@@ -2881,7 +2878,7 @@ public class PackageManagerService extends IPackageManager.Stub
+ mSdkVersion + "; regranting permissions for internal storage");
updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
}
- updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
+ updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
ver.sdkVersion = mSdkVersion;
// If this is the first boot or an update from pre-M, and it is a normal
@@ -3264,23 +3261,6 @@ public class PackageManagerService extends IPackageManager.Stub
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;
}
@@ -3368,7 +3348,9 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public boolean isUpgrade() {
// allow instant applications
- return mIsUpgrade;
+ // The system property allows testing ota flow when upgraded to the same image.
+ return mIsUpgrade || SystemProperties.getBoolean(
+ "persist.pm.mock-upgrade", false /* default */);
}
private @Nullable String getRequiredButNotReallyRequiredVerifierLPr() {
@@ -5182,7 +5164,7 @@ public class PackageManagerService extends IPackageManager.Stub
final PermissionsState permissionsState = settingBase.getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
if (isUidInstantApp) {
- if (mPermissionManager.isPermissionInstant(permName)) {
+ if (mSettings.mPermissions.isPermissionInstant(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
@@ -5251,8 +5233,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- boolean addPermission(PermissionInfo info, final boolean async) {
- return mPermissionManager.addPermission(
+ private boolean addDynamicPermission(PermissionInfo info, final boolean async) {
+ return mPermissionManager.addDynamicPermission(
info, async, getCallingUid(), new PermissionCallback() {
@Override
public void onPermissionChanged() {
@@ -5268,20 +5250,20 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public boolean addPermission(PermissionInfo info) {
synchronized (mPackages) {
- return addPermission(info, false);
+ return addDynamicPermission(info, false);
}
}
@Override
public boolean addPermissionAsync(PermissionInfo info) {
synchronized (mPackages) {
- return addPermission(info, true);
+ return addDynamicPermission(info, true);
}
}
@Override
public void removePermission(String permName) {
- mPermissionManager.removePermission(permName, getCallingUid(), mPermissionCallback);
+ mPermissionManager.removeDynamicPermission(permName, getCallingUid(), mPermissionCallback);
}
@Override
@@ -5942,17 +5924,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
- public String[] getAppOpPermissionPackages(String permissionName) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return null;
- }
- synchronized (mPackages) {
- ArraySet<String> pkgs = mAppOpPermissionPackages.get(permissionName);
- if (pkgs == null) {
- return null;
- }
- return pkgs.toArray(new String[pkgs.size()]);
- }
+ public String[] getAppOpPermissionPackages(String permName) {
+ return mPermissionManager.getAppOpPermissionPackages(permName);
}
@Override
@@ -9124,10 +9097,30 @@ public class PackageManagerService extends IPackageManager.Stub
// 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();
+ profileFile = new File(systemProfilePath);
+ // If we have a profile for a compressed APK, copy it to the reference
+ // location.
+ // Note that copying the profile here will cause it to override the
+ // reference profile every OTA even though the existing reference profile
+ // may have more data. We can't copy during decompression since the
+ // directories are not set up at that point.
+ 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, "Failed to copy system profile for stub package!");
+ } else {
+ useProfileForDexopt = true;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to copy profile " +
+ profileFile.getAbsolutePath() + " ", e);
+ }
+ }
}
}
}
@@ -10346,7 +10339,7 @@ public class PackageManagerService extends IPackageManager.Stub
Slog.i(TAG, "Adopting permissions from " + origName + " to "
+ pkg.packageName);
// SIDE EFFECTS; updates permissions system state; move elsewhere
- mSettings.transferPermissionsLPw(origName, pkg.packageName);
+ mSettings.mPermissions.transferPermissions(origName, pkg.packageName);
}
}
}
@@ -11194,54 +11187,13 @@ public class PackageManagerService extends IPackageManager.Stub
if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permission Groups: " + r);
}
- N = pkg.permissions.size();
- r = null;
- for (i=0; i<N; i++) {
- PackageParser.Permission p = pkg.permissions.get(i);
- // Dont allow ephemeral apps to define new permissions.
- if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
- Slog.w(TAG, "Permission " + p.info.name + " from package "
- + p.info.packageName
- + " ignored: instant apps cannot define new permissions.");
- continue;
- }
-
- // Assume by default that we did not install this permission into the system.
- p.info.flags &= ~PermissionInfo.FLAG_INSTALLED;
-
- // Now that permission groups have a special meaning, we ignore permission
- // groups for legacy apps to prevent unexpected behavior. In particular,
- // permissions for one app being granted to someone just because they happen
- // to be in a group defined by another app (before this had no implications).
- if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
- p.group = mPermissionGroups.get(p.info.group);
- // Warn for a permission in an unknown group.
- if (DEBUG_PERMISSIONS && p.info.group != null && p.group == null) {
- Slog.i(TAG, "Permission " + p.info.name + " from package "
- + p.info.packageName + " in an unknown group " + p.info.group);
- }
- }
-
- // 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);
- } else {
- final BasePermission bp = BasePermission.createOrUpdate(
- (BasePermission) mPermissionManager.getPermissionTEMP(p.info.name),
- p, pkg, mSettings.mPermissionTrees, chatty);
- mPermissionManager.putPermissionTEMP(p.info.name, bp);
- }
+ // Dont allow ephemeral apps to define new permissions.
+ if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
+ Slog.w(TAG, "Permissions from package " + pkg.packageName
+ + " ignored: instant apps cannot define new permissions.");
+ } else {
+ mPermissionManager.addAllPermissions(pkg, chatty);
}
N = pkg.instrumentation.size();
@@ -11967,53 +11919,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (DEBUG_REMOVE) Log.d(TAG, " Activities: " + r);
}
- N = pkg.permissions.size();
- r = null;
- for (i=0; i<N; i++) {
- PackageParser.Permission p = pkg.permissions.get(i);
- BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(p.info.name);
- if (bp == null) {
- bp = mSettings.mPermissionTrees.get(p.info.name);
- }
- if (bp != null && bp.isPermission(p)) {
- bp.setPermission(null);
- if (DEBUG_REMOVE && chatty) {
- if (r == null) {
- r = new StringBuilder(256);
- } else {
- r.append(' ');
- }
- r.append(p.info.name);
- }
- }
- if ((p.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
- ArraySet<String> appOpPkgs = mAppOpPermissionPackages.get(p.info.name);
- if (appOpPkgs != null) {
- appOpPkgs.remove(pkg.packageName);
- }
- }
- }
- if (r != null) {
- if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r);
- }
-
- N = pkg.requestedPermissions.size();
- r = null;
- for (i=0; i<N; i++) {
- String perm = pkg.requestedPermissions.get(i);
- if (mPermissionManager.isPermissionAppOp(perm)) {
- ArraySet<String> appOpPkgs = mAppOpPermissionPackages.get(perm);
- if (appOpPkgs != null) {
- appOpPkgs.remove(pkg.packageName);
- if (appOpPkgs.isEmpty()) {
- mAppOpPermissionPackages.remove(perm);
- }
- }
- }
- }
- if (r != null) {
- if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r);
- }
+ mPermissionManager.removeAllPermissions(pkg, chatty);
N = pkg.instrumentation.size();
r = null;
@@ -12074,18 +11980,9 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- private static boolean hasPermission(PackageParser.Package pkgInfo, String perm) {
- for (int i=pkgInfo.permissions.size()-1; i>=0; i--) {
- if (pkgInfo.permissions.get(i).info.name.equals(perm)) {
- return true;
- }
- }
- return false;
- }
-
- static final int UPDATE_PERMISSIONS_ALL = 1<<0;
- static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
- static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
+ public static final int UPDATE_PERMISSIONS_ALL = 1<<0;
+ public static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
+ public static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
private void updatePermissionsLPw(PackageParser.Package pkg, int flags) {
// Update the parent permissions
@@ -12101,10 +11998,10 @@ public class PackageManagerService extends IPackageManager.Stub
private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo,
int flags) {
final String volumeUuid = (pkgInfo != null) ? getVolumeUuidForPackage(pkgInfo) : null;
- updatePermissionsLPw(changingPkg, pkgInfo, volumeUuid, flags);
+ updatePermissionsLocked(changingPkg, pkgInfo, volumeUuid, flags);
}
- private void updatePermissionsLPw(String changingPkg,
+ private void updatePermissionsLocked(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
@@ -12116,55 +12013,11 @@ public class PackageManagerService extends IPackageManager.Stub
// 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.getSourcePackageSetting() == null) {
- // We may not yet have parsed the package, so just see if
- // we still know about its settings.
- bp.setSourcePackageSetting(mSettings.mPackages.get(bp.getSourcePackageName()));
- }
- 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.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();
- }
- }
- }
+ flags = mPermissionManager.updatePermissionTrees(changingPkg, pkgInfo, flags);
// Make sure all dynamic permissions have been assigned to a package,
// and make sure there are no dangling permissions.
- 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.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;
- permissionIter.remove();
- }
- }
- }
+ flags = mPermissionManager.updatePermissions(changingPkg, pkgInfo, flags);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions");
// Now update the permissions for all packages, in particular
@@ -12286,12 +12139,7 @@ public class PackageManagerService extends IPackageManager.Stub
// Keep track of app op permissions.
if (bp.isAppOp()) {
- ArraySet<String> pkgs = mAppOpPermissionPackages.get(perm);
- if (pkgs == null) {
- pkgs = new ArraySet<>();
- mAppOpPermissionPackages.put(perm, pkgs);
- }
- pkgs.add(pkg.packageName);
+ mSettings.addAppOpPackage(perm, pkg.packageName);
}
if (bp.isNormal()) {
@@ -21238,7 +21086,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
// permissions, ensure permissions are updated. Beware of dragons if you
// try optimizing this.
synchronized (mPackages) {
- updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL,
+ updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL,
UPDATE_PERMISSIONS_ALL);
}
@@ -21289,9 +21137,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL);
if (mPrivappPermissionsViolations != null) {
- Slog.wtf(TAG,"Signature|privileged permissions not in "
+ throw new IllegalStateException("Signature|privileged permissions not in "
+ "privapp-permissions whitelist: " + mPrivappPermissionsViolations);
- mPrivappPermissionsViolations = null;
}
}
@@ -21784,22 +21631,6 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
mSettings.dumpPermissionsLPr(pw, packageName, permissionNames, dumpState);
- if (packageName == null && permissionNames == null) {
- for (int iperm=0; iperm<mAppOpPermissionPackages.size(); iperm++) {
- if (iperm == 0) {
- if (dumpState.onTitlePrinted())
- pw.println();
- pw.println("AppOp Permissions:");
- }
- pw.print(" AppOp Permission ");
- pw.print(mAppOpPermissionPackages.keyAt(iperm));
- pw.println(":");
- ArraySet<String> pkgs = mAppOpPermissionPackages.valueAt(iperm);
- for (int ipkg=0; ipkg<pkgs.size(); ipkg++) {
- pw.print(" "); pw.println(pkgs.valueAt(ipkg));
- }
- }
- }
}
if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
@@ -22029,11 +21860,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
synchronized (mAvailableFeatures) {
final int count = mAvailableFeatures.size();
for (int i = 0; i < count; i++) {
- final FeatureInfo feat = mAvailableFeatures.valueAt(i);
- final long featureToken = proto.start(PackageServiceDumpProto.FEATURES);
- proto.write(PackageServiceDumpProto.FeatureProto.NAME, feat.name);
- proto.write(PackageServiceDumpProto.FeatureProto.VERSION, feat.version);
- proto.end(featureToken);
+ mAvailableFeatures.valueAt(i).writeToProto(proto, PackageServiceDumpProto.FEATURES);
}
}
}
@@ -22065,7 +21892,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
private void dumpDexoptStateLPr(PrintWriter pw, String packageName) {
- final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.println();
ipw.println("Dexopt state:");
ipw.increaseIndent();
@@ -22092,7 +21919,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
private void dumpCompilerStatsLPr(PrintWriter pw, String packageName) {
- final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.println();
ipw.println("Compiler stats:");
ipw.increaseIndent();
@@ -22303,7 +22130,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
+ mSdkVersion + "; regranting permissions for " + volumeUuid);
updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
}
- updatePermissionsLPw(null, null, volumeUuid, updateFlags);
+ updatePermissionsLocked(null, null, volumeUuid, updateFlags);
// Yay, everything is now upgraded
ver.forceCurrent();
@@ -23311,15 +23138,15 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
void onNewUserCreated(final int 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.
- // Hence, if a new user is added we have to propagate dangerous
- // permission grants for these legacy apps.
- if (mPermissionReviewRequired) {
- updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
- | UPDATE_PERMISSIONS_REPLACE_ALL);
+ // 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.
+ // Hence, if a new user is added we have to propagate dangerous
+ // permission grants for these legacy apps.
+ if (mPermissionReviewRequired) {
+ updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
+ | UPDATE_PERMISSIONS_REPLACE_ALL);
+ }
}
}
@@ -23763,12 +23590,12 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
@Override
- public Object enforcePermissionTreeTEMP(String permName, int callingUid) {
+ public PackageParser.PermissionGroup getPermissionGroupTEMP(String groupName) {
synchronized (mPackages) {
- return BasePermission.enforcePermissionTreeLP(
- mSettings.mPermissionTrees, permName, callingUid);
+ return mPermissionGroups.get(groupName);
}
}
+
@Override
public boolean isInstantApp(String packageName, int userId) {
return PackageManagerService.this.isInstantApp(packageName, userId);
diff --git a/com/android/server/pm/Settings.java b/com/android/server/pm/Settings.java
index 00844114..191b43a6 100644
--- a/com/android/server/pm/Settings.java
+++ b/com/android/server/pm/Settings.java
@@ -378,10 +378,6 @@ public final class Settings {
private final ArrayMap<Long, Integer> mKeySetRefs =
new ArrayMap<Long, Integer>();
- // Mapping from permission tree names to info about them.
- final ArrayMap<String, BasePermission> mPermissionTrees =
- new ArrayMap<String, BasePermission>();
-
// Packages that have been uninstalled and still need their external
// storage data deleted.
final ArrayList<PackageCleanItem> mPackagesToBeCleaned = new ArrayList<PackageCleanItem>();
@@ -416,7 +412,7 @@ public final class Settings {
public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages);
/** Settings and other information about permissions */
- private final PermissionSettings mPermissions;
+ final PermissionSettings mPermissions;
Settings(PermissionSettings permissions, Object lock) {
this(Environment.getDataDirectory(), permissions, lock);
@@ -622,6 +618,10 @@ public final class Settings {
return null;
}
+ void addAppOpPackage(String permName, String packageName) {
+ mPermissions.addAppOpPackage(permName, packageName);
+ }
+
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
SharedUserSetting s = mSharedUsers.get(name);
if (s != null) {
@@ -666,13 +666,6 @@ public final class Settings {
}
/**
- * Transfers ownership of permissions from one package to another.
- */
- void transferPermissionsLPw(String origPackageName, String newPackageName) {
- mPermissions.transferPermissions(origPackageName, newPackageName, mPermissionTrees);
- }
-
- /**
* Creates a new {@code PackageSetting} object.
* Use this method instead of the constructor to ensure a settings object is created
* with the correct base.
@@ -2496,9 +2489,7 @@ public final class Settings {
}
serializer.startTag(null, "permission-trees");
- for (BasePermission bp : mPermissionTrees.values()) {
- writePermissionLPr(serializer, bp);
- }
+ mPermissions.writePermissionTrees(serializer);
serializer.endTag(null, "permission-trees");
serializer.startTag(null, "permissions");
@@ -3042,7 +3033,7 @@ public final class Settings {
} else if (tagName.equals("permissions")) {
mPermissions.readPermissions(parser);
} else if (tagName.equals("permission-trees")) {
- PermissionSettings.readPermissions(mPermissionTrees, parser);
+ mPermissions.readPermissionTrees(parser);
} else if (tagName.equals("shared-user")) {
readSharedUserLPw(parser);
} else if (tagName.equals("preferred-packages")) {
@@ -4938,11 +4929,7 @@ public final class Settings {
void dumpSharedUsersProto(ProtoOutputStream proto) {
final int count = mSharedUsers.size();
for (int i = 0; i < count; i++) {
- final SharedUserSetting su = mSharedUsers.valueAt(i);
- final long sharedUserToken = proto.start(PackageServiceDumpProto.SHARED_USERS);
- proto.write(PackageServiceDumpProto.SharedUserProto.USER_ID, su.userId);
- proto.write(PackageServiceDumpProto.SharedUserProto.NAME, su.name);
- proto.end(sharedUserToken);
+ mSharedUsers.valueAt(i).writeToProto(proto, PackageServiceDumpProto.SHARED_USERS);
}
}
diff --git a/com/android/server/pm/SharedUserSetting.java b/com/android/server/pm/SharedUserSetting.java
index a0dadae3..877da144 100644
--- a/com/android/server/pm/SharedUserSetting.java
+++ b/com/android/server/pm/SharedUserSetting.java
@@ -18,7 +18,9 @@ package com.android.server.pm;
import android.annotation.Nullable;
import android.content.pm.PackageParser;
+import android.service.pm.PackageServiceDumpProto;
import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
import java.util.ArrayList;
import java.util.Collection;
@@ -53,6 +55,13 @@ public final class SharedUserSetting extends SettingBase {
+ name + "/" + userId + "}";
}
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(PackageServiceDumpProto.SharedUserProto.USER_ID, userId);
+ proto.write(PackageServiceDumpProto.SharedUserProto.NAME, name);
+ proto.end(token);
+ }
+
void removePackage(PackageSetting packageSetting) {
if (packages.remove(packageSetting)) {
// recalculate the pkgFlags for this shared user if needed
diff --git a/com/android/server/pm/ShortcutBitmapSaver.java b/com/android/server/pm/ShortcutBitmapSaver.java
index 4f5d1560..815f8851 100644
--- a/com/android/server/pm/ShortcutBitmapSaver.java
+++ b/com/android/server/pm/ShortcutBitmapSaver.java
@@ -21,6 +21,8 @@ import android.content.pm.ShortcutInfo;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.drawable.Icon;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
import android.os.SystemClock;
import android.util.Log;
import android.util.Slog;
@@ -165,7 +167,13 @@ public class ShortcutBitmapSaver {
// Compress it and enqueue to the requests.
final byte[] bytes;
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
try {
+ // compress() triggers a slow call, but in this case it's needed to save RAM and also
+ // the target bitmap is of an icon size, so let's just permit it.
+ StrictMode.setThreadPolicy(new ThreadPolicy.Builder(oldPolicy)
+ .permitCustomSlowCalls()
+ .build());
final Bitmap shrunk = mService.shrinkBitmap(original, maxDimension);
try {
try (final ByteArrayOutputStream out = new ByteArrayOutputStream(64 * 1024)) {
@@ -184,6 +192,8 @@ public class ShortcutBitmapSaver {
} catch (IOException | RuntimeException | OutOfMemoryError e) {
Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
return;
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
}
shortcut.addFlags(
diff --git a/com/android/server/pm/ShortcutLauncher.java b/com/android/server/pm/ShortcutLauncher.java
index f922ad19..cedf4763 100644
--- a/com/android/server/pm/ShortcutLauncher.java
+++ b/com/android/server/pm/ShortcutLauncher.java
@@ -83,11 +83,16 @@ class ShortcutLauncher extends ShortcutPackageItem {
return mOwnerUserId;
}
+ @Override
+ protected boolean canRestoreAnyVersion() {
+ // Launcher's pinned shortcuts can be restored to an older version.
+ return true;
+ }
+
/**
* Called when the new package can't receive the backup, due to signature or version mismatch.
*/
- @Override
- protected void onRestoreBlocked() {
+ private void onRestoreBlocked() {
final ArrayList<PackageWithUser> pinnedPackages =
new ArrayList<>(mPinnedShortcuts.keySet());
mPinnedShortcuts.clear();
@@ -101,15 +106,21 @@ class ShortcutLauncher extends ShortcutPackageItem {
}
@Override
- protected void onRestored() {
- // Nothing to do.
+ protected void onRestored(int restoreBlockReason) {
+ // For launcher, possible reasons here are DISABLED_REASON_SIGNATURE_MISMATCH or
+ // DISABLED_REASON_BACKUP_NOT_SUPPORTED.
+ // DISABLED_REASON_VERSION_LOWER will NOT happen because we don't check version
+ // code for launchers.
+ if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ onRestoreBlocked();
+ }
}
/**
* Pin the given shortcuts, replacing the current pinned ones.
*/
public void pinShortcuts(@UserIdInt int packageUserId,
- @NonNull String packageName, @NonNull List<String> ids) {
+ @NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) {
final ShortcutPackage packageShortcuts =
mShortcutUser.getPackageShortcutsIfExists(packageName);
if (packageShortcuts == null) {
@@ -124,8 +135,12 @@ class ShortcutLauncher extends ShortcutPackageItem {
} else {
final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
- // Pin shortcuts. Make sure only pin the ones that were visible to the caller.
- // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
+ // Actually pin shortcuts.
+ // This logic here is to make sure a launcher cannot pin a shortcut that is floating
+ // (i.e. not dynamic nor manifest but is pinned) and pinned by another launcher.
+ // In this case, technically the shortcut doesn't exist to this launcher, so it can't
+ // pin it.
+ // (Maybe unnecessarily strict...)
final ArraySet<String> newSet = new ArraySet<>();
@@ -135,8 +150,10 @@ class ShortcutLauncher extends ShortcutPackageItem {
if (si == null) {
continue;
}
- if (si.isDynamic() || si.isManifestShortcut()
- || (prevSet != null && prevSet.contains(id))) {
+ if (si.isDynamic()
+ || si.isManifestShortcut()
+ || (prevSet != null && prevSet.contains(id))
+ || forPinRequest) {
newSet.add(id);
}
}
@@ -155,7 +172,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
}
/**
- * Return true if the given shortcut is pinned by this launcher.
+ * Return true if the given shortcut is pinned by this launcher.<code></code>
*/
public boolean hasPinned(ShortcutInfo shortcut) {
final ArraySet<String> pinned =
@@ -164,10 +181,10 @@ class ShortcutLauncher extends ShortcutPackageItem {
}
/**
- * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List)}
+ * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List, boolean)}
*/
public void addPinnedShortcut(@NonNull String packageName, @UserIdInt int packageUserId,
- String id) {
+ String id, boolean forPinRequest) {
final ArraySet<String> pinnedSet = getPinnedShortcutIds(packageName, packageUserId);
final ArrayList<String> pinnedList;
if (pinnedSet != null) {
@@ -178,21 +195,21 @@ class ShortcutLauncher extends ShortcutPackageItem {
}
pinnedList.add(id);
- pinShortcuts(packageUserId, packageName, pinnedList);
+ pinShortcuts(packageUserId, packageName, pinnedList, forPinRequest);
}
boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
}
- public void ensureVersionInfo() {
+ public void ensurePackageInfo() {
final PackageInfo pi = mShortcutUser.mService.getPackageInfoWithSignatures(
getPackageName(), getPackageUserId());
if (pi == null) {
Slog.w(TAG, "Package not found: " + getPackageName());
return;
}
- getPackageInfo().updateVersionInfo(pi);
+ getPackageInfo().updateFromPackageInfo(pi);
}
/**
@@ -201,6 +218,10 @@ class ShortcutLauncher extends ShortcutPackageItem {
@Override
public void saveToXml(XmlSerializer out, boolean forBackup)
throws IOException {
+ if (forBackup && !getPackageInfo().isBackupAllowed()) {
+ // If an launcher app doesn't support backup&restore, then nothing to do.
+ return;
+ }
final int size = mPinnedShortcuts.size();
if (size == 0) {
return; // Nothing to write.
@@ -209,7 +230,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
out.startTag(null, TAG_ROOT);
ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, getPackageName());
ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, getPackageUserId());
- getPackageInfo().saveToXml(out);
+ getPackageInfo().saveToXml(out, forBackup);
for (int i = 0; i < size; i++) {
final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
diff --git a/com/android/server/pm/ShortcutPackage.java b/com/android/server/pm/ShortcutPackage.java
index 6fc1e738..12f490fa 100644
--- a/com/android/server/pm/ShortcutPackage.java
+++ b/com/android/server/pm/ShortcutPackage.java
@@ -84,6 +84,7 @@ class ShortcutPackage extends ShortcutPackageItem {
private static final String ATTR_DISABLED_MESSAGE = "dmessage";
private static final String ATTR_DISABLED_MESSAGE_RES_ID = "dmessageid";
private static final String ATTR_DISABLED_MESSAGE_RES_NAME = "dmessagename";
+ private static final String ATTR_DISABLED_REASON = "disabled-reason";
private static final String ATTR_INTENT_LEGACY = "intent";
private static final String ATTR_INTENT_NO_EXTRA = "intent-base";
private static final String ATTR_RANK = "rank";
@@ -156,13 +157,25 @@ class ShortcutPackage extends ShortcutPackageItem {
}
@Override
- protected void onRestoreBlocked() {
- // Can't restore due to version/signature mismatch. Remove all shortcuts.
- mShortcuts.clear();
+ protected boolean canRestoreAnyVersion() {
+ return false;
}
@Override
- protected void onRestored() {
+ protected void onRestored(int restoreBlockReason) {
+ // Shortcuts have been restored.
+ // - Unshadow all shortcuts.
+ // - Set disabled reason.
+ // - Disable if needed.
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ ShortcutInfo si = mShortcuts.valueAt(i);
+ si.clearFlags(ShortcutInfo.FLAG_SHADOW);
+
+ si.setDisabledReason(restoreBlockReason);
+ if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ si.addFlags(ShortcutInfo.FLAG_DISABLED);
+ }
+ }
// Because some launchers may not have been restored (e.g. allowBackup=false),
// we need to re-calculate the pinned shortcuts.
refreshPinnedFlags();
@@ -176,31 +189,47 @@ class ShortcutPackage extends ShortcutPackageItem {
return mShortcuts.get(id);
}
- private void ensureNotImmutable(@Nullable ShortcutInfo shortcut) {
- if (shortcut != null && shortcut.isImmutable()) {
+ public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
+ ShortcutInfo si = findShortcutById(id);
+ return si != null && !si.isVisibleToPublisher();
+ }
+
+ public boolean isShortcutExistsAndVisibleToPublisher(String id) {
+ ShortcutInfo si = findShortcutById(id);
+ return si != null && si.isVisibleToPublisher();
+ }
+
+ private void ensureNotImmutable(@Nullable ShortcutInfo shortcut, boolean ignoreInvisible) {
+ if (shortcut != null && shortcut.isImmutable()
+ && (!ignoreInvisible || shortcut.isVisibleToPublisher())) {
throw new IllegalArgumentException(
"Manifest shortcut ID=" + shortcut.getId()
+ " may not be manipulated via APIs");
}
}
- public void ensureNotImmutable(@NonNull String id) {
- ensureNotImmutable(mShortcuts.get(id));
+ public void ensureNotImmutable(@NonNull String id, boolean ignoreInvisible) {
+ ensureNotImmutable(mShortcuts.get(id), ignoreInvisible);
}
- public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds) {
+ public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds,
+ boolean ignoreInvisible) {
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
- ensureNotImmutable(shortcutIds.get(i));
+ ensureNotImmutable(shortcutIds.get(i), ignoreInvisible);
}
}
- public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts) {
+ public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts,
+ boolean ignoreInvisible) {
for (int i = shortcuts.size() - 1; i >= 0; i--) {
- ensureNotImmutable(shortcuts.get(i).getId());
+ ensureNotImmutable(shortcuts.get(i).getId(), ignoreInvisible);
}
}
- private ShortcutInfo deleteShortcutInner(@NonNull String id) {
+ /**
+ * Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
+ */
+ private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
final ShortcutInfo shortcut = mShortcuts.remove(id);
if (shortcut != null) {
mShortcutUser.mService.removeIconLocked(shortcut);
@@ -210,10 +239,14 @@ class ShortcutPackage extends ShortcutPackageItem {
return shortcut;
}
- private void addShortcutInner(@NonNull ShortcutInfo newShortcut) {
+ /**
+ * Force replace a shortcut. If there's already a shortcut with the same ID, it'll be removed,
+ * even if it's invisible.
+ */
+ private void forceReplaceShortcutInner(@NonNull ShortcutInfo newShortcut) {
final ShortcutService s = mShortcutUser.mService;
- deleteShortcutInner(newShortcut.getId());
+ forceDeleteShortcutInner(newShortcut.getId());
// Extract Icon and update the icon res ID and the bitmap path.
s.saveIconAndFixUpShortcutLocked(newShortcut);
@@ -222,11 +255,12 @@ class ShortcutPackage extends ShortcutPackageItem {
}
/**
- * Add a shortcut, or update one with the same ID, with taking over existing flags.
+ * Add a shortcut. If there's already a one with the same ID, it'll be removed, even if it's
+ * invisible.
*
* It checks the max number of dynamic shortcuts.
*/
- public void addOrUpdateDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
+ public void addOrReplaceDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
Preconditions.checkArgument(newShortcut.isEnabled(),
"add/setDynamicShortcuts() cannot publish disabled shortcuts");
@@ -242,7 +276,7 @@ class ShortcutPackage extends ShortcutPackageItem {
} else {
// It's an update case.
// Make sure the target is updatable. (i.e. should be mutable.)
- oldShortcut.ensureUpdatableWith(newShortcut);
+ oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false);
wasPinned = oldShortcut.isPinned();
}
@@ -252,7 +286,7 @@ class ShortcutPackage extends ShortcutPackageItem {
newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
}
- addShortcutInner(newShortcut);
+ forceReplaceShortcutInner(newShortcut);
}
/**
@@ -273,7 +307,7 @@ class ShortcutPackage extends ShortcutPackageItem {
}
if (removeList != null) {
for (int i = removeList.size() - 1; i >= 0; i--) {
- deleteShortcutInner(removeList.get(i));
+ forceDeleteShortcutInner(removeList.get(i));
}
}
}
@@ -281,13 +315,13 @@ class ShortcutPackage extends ShortcutPackageItem {
/**
* Remove all dynamic shortcuts.
*/
- public void deleteAllDynamicShortcuts() {
+ public void deleteAllDynamicShortcuts(boolean ignoreInvisible) {
final long now = mShortcutUser.mService.injectCurrentTimeMillis();
boolean changed = false;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
- if (si.isDynamic()) {
+ if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) {
changed = true;
si.setTimestamp(now);
@@ -307,9 +341,10 @@ class ShortcutPackage extends ShortcutPackageItem {
* @return true if it's actually removed because it wasn't pinned, or false if it's still
* pinned.
*/
- public boolean deleteDynamicWithId(@NonNull String shortcutId) {
+ public boolean deleteDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible) {
final ShortcutInfo removed = deleteOrDisableWithId(
- shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
+ shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false, ignoreInvisible,
+ ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
return removed == null;
}
@@ -320,9 +355,11 @@ class ShortcutPackage extends ShortcutPackageItem {
* @return true if it's actually removed because it wasn't pinned, or false if it's still
* pinned.
*/
- private boolean disableDynamicWithId(@NonNull String shortcutId) {
+ private boolean disableDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible,
+ int disabledReason) {
final ShortcutInfo disabled = deleteOrDisableWithId(
- shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false);
+ shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false, ignoreInvisible,
+ disabledReason);
return disabled == null;
}
@@ -331,9 +368,10 @@ class ShortcutPackage extends ShortcutPackageItem {
* is pinned, it'll remain as a pinned shortcut but will be disabled.
*/
public void disableWithId(@NonNull String shortcutId, String disabledMessage,
- int disabledMessageResId, boolean overrideImmutable) {
+ int disabledMessageResId, boolean overrideImmutable, boolean ignoreInvisible,
+ int disabledReason) {
final ShortcutInfo disabled = deleteOrDisableWithId(shortcutId, /* disable =*/ true,
- overrideImmutable);
+ overrideImmutable, ignoreInvisible, disabledReason);
if (disabled != null) {
if (disabledMessage != null) {
@@ -348,14 +386,18 @@ class ShortcutPackage extends ShortcutPackageItem {
@Nullable
private ShortcutInfo deleteOrDisableWithId(@NonNull String shortcutId, boolean disable,
- boolean overrideImmutable) {
+ boolean overrideImmutable, boolean ignoreInvisible, int disabledReason) {
+ Preconditions.checkState(
+ (disable == (disabledReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED)),
+ "disable and disabledReason disagree: " + disable + " vs " + disabledReason);
final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
- if (oldShortcut == null || !oldShortcut.isEnabled()) {
+ if (oldShortcut == null || !oldShortcut.isEnabled()
+ && (ignoreInvisible && !oldShortcut.isVisibleToPublisher())) {
return null; // Doesn't exist or already disabled.
}
if (!overrideImmutable) {
- ensureNotImmutable(oldShortcut);
+ ensureNotImmutable(oldShortcut, /*ignoreInvisible=*/ true);
}
if (oldShortcut.isPinned()) {
@@ -363,6 +405,10 @@ class ShortcutPackage extends ShortcutPackageItem {
oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
if (disable) {
oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
+ // Do not overwrite the disabled reason if one is alreay set.
+ if (oldShortcut.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ oldShortcut.setDisabledReason(disabledReason);
+ }
}
oldShortcut.setTimestamp(mShortcutUser.mService.injectCurrentTimeMillis());
@@ -373,7 +419,7 @@ class ShortcutPackage extends ShortcutPackageItem {
return oldShortcut;
} else {
- deleteShortcutInner(shortcutId);
+ forceDeleteShortcutInner(shortcutId);
return null;
}
}
@@ -381,11 +427,25 @@ class ShortcutPackage extends ShortcutPackageItem {
public void enableWithId(@NonNull String shortcutId) {
final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
if (shortcut != null) {
- ensureNotImmutable(shortcut);
+ ensureNotImmutable(shortcut, /*ignoreInvisible=*/ true);
shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
+ shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
}
}
+ public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) {
+ final ShortcutInfo source = mShortcuts.get(shortcut.getId());
+ Preconditions.checkNotNull(source);
+
+ mShortcutUser.mService.validateShortcutForPinRequest(shortcut);
+
+ shortcut.addFlags(ShortcutInfo.FLAG_PINNED);
+
+ forceReplaceShortcutInner(shortcut);
+
+ adjustRanks();
+ }
+
/**
* Called after a launcher updates the pinned set. For each shortcut in this package,
* set FLAG_PINNED if any launcher has pinned it. Otherwise, clear it.
@@ -510,7 +570,7 @@ class ShortcutPackage extends ShortcutPackageItem {
*/
public void findAll(@NonNull List<ShortcutInfo> result,
@Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
- findAll(result, query, cloneFlag, null, 0);
+ findAll(result, query, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false);
}
/**
@@ -522,7 +582,7 @@ class ShortcutPackage extends ShortcutPackageItem {
*/
public void findAll(@NonNull List<ShortcutInfo> result,
@Nullable Predicate<ShortcutInfo> query, int cloneFlag,
- @Nullable String callingLauncher, int launcherUserId) {
+ @Nullable String callingLauncher, int launcherUserId, boolean getPinnedByAnyLauncher) {
if (getPackageInfo().isShadow()) {
// Restored and the app not installed yet, so don't return any.
return;
@@ -544,9 +604,11 @@ class ShortcutPackage extends ShortcutPackageItem {
final boolean isPinnedByCaller = (callingLauncher == null)
|| ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
- if (si.isFloating()) {
- if (!isPinnedByCaller) {
- continue;
+ if (!getPinnedByAnyLauncher) {
+ if (si.isFloating()) {
+ if (!isPinnedByCaller) {
+ continue;
+ }
}
}
final ShortcutInfo clone = si.clone(cloneFlag);
@@ -693,7 +755,27 @@ class ShortcutPackage extends ShortcutPackageItem {
getPackageInfo().getVersionCode(), pi.versionCode));
}
- getPackageInfo().updateVersionInfo(pi);
+ getPackageInfo().updateFromPackageInfo(pi);
+ final int newVersionCode = getPackageInfo().getVersionCode();
+
+ // See if there are any shortcuts that were prevented restoring because the app was of a
+ // lower version, and re-enable them.
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+ if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
+ continue;
+ }
+ if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) {
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format("Shortcut %s require version %s, still not restored.",
+ si.getId(), getPackageInfo().getBackupSourceVersionCode()));
+ }
+ continue;
+ }
+ Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId()));
+ si.clearFlags(ShortcutInfo.FLAG_DISABLED);
+ si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
+ }
// For existing shortcuts, update timestamps if they have any resources.
// Also check if shortcuts' activities are still main activities. Otherwise, disable them.
@@ -713,7 +795,8 @@ class ShortcutPackage extends ShortcutPackageItem {
Slog.w(TAG, String.format(
"%s is no longer main activity. Disabling shorcut %s.",
getPackageName(), si.getId()));
- if (disableDynamicWithId(si.getId())) {
+ if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false,
+ ShortcutInfo.DISABLED_REASON_APP_CHANGED)) {
continue; // Actually removed.
}
// Still pinned, so fall-through and possibly update the resources.
@@ -809,7 +892,7 @@ class ShortcutPackage extends ShortcutPackageItem {
// Note even if enabled=false, we still need to update all fields, so do it
// regardless.
- addShortcutInner(newShortcut); // This will clean up the old one too.
+ forceReplaceShortcutInner(newShortcut); // This will clean up the old one too.
if (!newDisabled && toDisableList != null) {
// Still alive, don't remove.
@@ -831,7 +914,8 @@ class ShortcutPackage extends ShortcutPackageItem {
final String id = toDisableList.valueAt(i);
disableWithId(id, /* disable message =*/ null, /* disable message resid */ 0,
- /* overrideImmutable=*/ true);
+ /* overrideImmutable=*/ true, /*ignoreInvisible=*/ false,
+ ShortcutInfo.DISABLED_REASON_APP_CHANGED);
}
removeOrphans();
}
@@ -869,7 +953,7 @@ class ShortcutPackage extends ShortcutPackageItem {
service.wtf("Found manifest shortcuts in excess list.");
continue;
}
- deleteDynamicWithId(shortcut.getId());
+ deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true);
}
}
@@ -1075,7 +1159,7 @@ class ShortcutPackage extends ShortcutPackageItem {
if (ret != 0) {
return ret;
}
- // If they're stil tie, just sort by their IDs.
+ // If they're still tie, just sort by their IDs.
// This may happen with updateShortcuts() -- see
// the testUpdateShortcuts_noManifestShortcuts() test.
return a.getId().compareTo(b.getId());
@@ -1257,25 +1341,34 @@ class ShortcutPackage extends ShortcutPackageItem {
ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
- getPackageInfo().saveToXml(out);
+ getPackageInfo().saveToXml(out, forBackup);
for (int j = 0; j < size; j++) {
- saveShortcut(out, mShortcuts.valueAt(j), forBackup);
+ saveShortcut(out, mShortcuts.valueAt(j), forBackup,
+ getPackageInfo().isBackupAllowed());
}
out.endTag(null, TAG_ROOT);
}
- private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup)
+ private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup,
+ boolean appSupportsBackup)
throws IOException, XmlPullParserException {
final ShortcutService s = mShortcutUser.mService;
if (forBackup) {
if (!(si.isPinned() && si.isEnabled())) {
- return; // We only backup pinned shortcuts that are enabled.
+ // We only backup pinned shortcuts that are enabled.
+ // Note, this means, shortcuts that are restored but are blocked restore, e.g. due
+ // to a lower version code, will not be ported to a new device.
+ return;
}
}
+ final boolean shouldBackupDetails =
+ !forBackup // It's not backup
+ || appSupportsBackup; // Or, it's a backup and app supports backup.
+
// Note: at this point no shortcuts should have bitmaps pending save, but if they do,
// just remove the bitmap.
if (si.isIconPendingSave()) {
@@ -1292,20 +1385,31 @@ class ShortcutPackage extends ShortcutPackageItem {
ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName());
- ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage());
- ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID,
- si.getDisabledMessageResourceId());
- ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
- si.getDisabledMessageResName());
+ if (shouldBackupDetails) {
+ ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage());
+ ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID,
+ si.getDisabledMessageResourceId());
+ ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
+ si.getDisabledMessageResName());
+ }
+ ShortcutService.writeAttr(out, ATTR_DISABLED_REASON, si.getDisabledReason());
ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
si.getLastChangedTimestamp());
if (forBackup) {
// Don't write icon information. Also drop the dynamic flag.
- ShortcutService.writeAttr(out, ATTR_FLAGS,
- si.getFlags() &
- ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
+
+ int flags = si.getFlags() &
+ ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
| ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE
- | ShortcutInfo.FLAG_DYNAMIC));
+ | ShortcutInfo.FLAG_DYNAMIC);
+ ShortcutService.writeAttr(out, ATTR_FLAGS, flags);
+
+ // Set the publisher version code at every backup.
+ final int packageVersionCode = getPackageInfo().getVersionCode();
+ if (packageVersionCode == 0) {
+ s.wtf("Package version code should be available at this point.");
+ // However, 0 is a valid version code, so we just go ahead with it...
+ }
} else {
// When writing for backup, ranks shouldn't be saved, since shortcuts won't be restored
// as dynamic.
@@ -1317,26 +1421,28 @@ class ShortcutPackage extends ShortcutPackageItem {
ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath());
}
- {
- final Set<String> cat = si.getCategories();
- if (cat != null && cat.size() > 0) {
- out.startTag(null, TAG_CATEGORIES);
- XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]),
- NAME_CATEGORIES, out);
- out.endTag(null, TAG_CATEGORIES);
+ if (shouldBackupDetails) {
+ {
+ final Set<String> cat = si.getCategories();
+ if (cat != null && cat.size() > 0) {
+ out.startTag(null, TAG_CATEGORIES);
+ XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]),
+ NAME_CATEGORIES, out);
+ out.endTag(null, TAG_CATEGORIES);
+ }
+ }
+ final Intent[] intentsNoExtras = si.getIntentsNoExtras();
+ final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
+ final int numIntents = intentsNoExtras.length;
+ for (int i = 0; i < numIntents; i++) {
+ out.startTag(null, TAG_INTENT);
+ ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
+ ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
+ out.endTag(null, TAG_INTENT);
}
- }
- final Intent[] intentsNoExtras = si.getIntentsNoExtras();
- final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
- final int numIntents = intentsNoExtras.length;
- for (int i = 0; i < numIntents; i++) {
- out.startTag(null, TAG_INTENT);
- ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
- ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
- out.endTag(null, TAG_INTENT);
- }
- ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
+ ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
+ }
out.endTag(null, TAG_SHORTCUT);
}
@@ -1356,6 +1462,7 @@ class ShortcutPackage extends ShortcutPackageItem {
ret.mLastResetTime =
ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
+
final int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1369,10 +1476,11 @@ class ShortcutPackage extends ShortcutPackageItem {
switch (tag) {
case ShortcutPackageInfo.TAG_ROOT:
ret.getPackageInfo().loadFromXml(parser, fromBackup);
+
continue;
case TAG_SHORTCUT:
final ShortcutInfo si = parseShortcut(parser, packageName,
- shortcutUser.getUserId());
+ shortcutUser.getUserId(), fromBackup);
// Don't use addShortcut(), we don't need to save the icon.
ret.mShortcuts.put(si.getId(), si);
@@ -1385,7 +1493,8 @@ class ShortcutPackage extends ShortcutPackageItem {
}
private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName,
- @UserIdInt int userId) throws IOException, XmlPullParserException {
+ @UserIdInt int userId, boolean fromBackup)
+ throws IOException, XmlPullParserException {
String id;
ComponentName activityComponent;
// Icon icon;
@@ -1398,6 +1507,7 @@ class ShortcutPackage extends ShortcutPackageItem {
String disabledMessage;
int disabledMessageResId;
String disabledMessageResName;
+ int disabledReason;
Intent intentLegacy;
PersistableBundle intentPersistableExtrasLegacy = null;
ArrayList<Intent> intents = new ArrayList<>();
@@ -1408,6 +1518,7 @@ class ShortcutPackage extends ShortcutPackageItem {
int iconResId;
String iconResName;
String bitmapPath;
+ int backupVersionCode;
ArraySet<String> categories = null;
id = ShortcutService.parseStringAttribute(parser, ATTR_ID);
@@ -1424,6 +1535,7 @@ class ShortcutPackage extends ShortcutPackageItem {
ATTR_DISABLED_MESSAGE_RES_ID);
disabledMessageResName = ShortcutService.parseStringAttribute(parser,
ATTR_DISABLED_MESSAGE_RES_NAME);
+ disabledReason = ShortcutService.parseIntAttribute(parser, ATTR_DISABLED_REASON);
intentLegacy = ShortcutService.parseIntentAttributeNoDefault(parser, ATTR_INTENT_LEGACY);
rank = (int) ShortcutService.parseLongAttribute(parser, ATTR_RANK);
lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP);
@@ -1480,6 +1592,19 @@ class ShortcutPackage extends ShortcutPackageItem {
intents.add(intentLegacy);
}
+
+ if ((disabledReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)
+ && ((flags & ShortcutInfo.FLAG_DISABLED) != 0)) {
+ // We didn't used to have the disabled reason, so if a shortcut is disabled
+ // and has no reason, we assume it was disabled by publisher.
+ disabledReason = ShortcutInfo.DISABLED_REASON_BY_APP;
+ }
+
+ // All restored shortcuts are initially "shadow".
+ if (fromBackup) {
+ flags |= ShortcutInfo.FLAG_SHADOW;
+ }
+
return new ShortcutInfo(
userId, id, packageName, activityComponent, /* icon =*/ null,
title, titleResId, titleResName, text, textResId, textResName,
@@ -1487,7 +1612,7 @@ class ShortcutPackage extends ShortcutPackageItem {
categories,
intents.toArray(new Intent[intents.size()]),
rank, extras, lastChangedTimestamp, flags,
- iconResId, iconResName, bitmapPath);
+ iconResId, iconResName, bitmapPath, disabledReason);
}
private static Intent parseIntent(XmlPullParser parser)
@@ -1602,6 +1727,20 @@ class ShortcutPackage extends ShortcutPackageItem {
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both resource and bitmap icons");
}
+ if (si.isEnabled()
+ != (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) {
+ failed = true;
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ + " isEnabled() and getDisabledReason() disagree: "
+ + si.isEnabled() + " vs " + si.getDisabledReason());
+ }
+ if ((si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
+ && (getPackageInfo().getBackupSourceVersionCode()
+ == ShortcutInfo.VERSION_CODE_UNKNOWN)) {
+ failed = true;
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ + " RESTORED_VERSION_LOWER with no backup source version code.");
+ }
if (s.isDummyMainActivity(si.getActivity())) {
failed = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
diff --git a/com/android/server/pm/ShortcutPackageInfo.java b/com/android/server/pm/ShortcutPackageInfo.java
index e5a2f5ac..3a9bbc89 100644
--- a/com/android/server/pm/ShortcutPackageInfo.java
+++ b/com/android/server/pm/ShortcutPackageInfo.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.pm.PackageInfo;
+import android.content.pm.ShortcutInfo;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -45,32 +46,45 @@ class ShortcutPackageInfo {
static final String TAG_ROOT = "package-info";
private static final String ATTR_VERSION = "version";
private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time";
+ private static final String ATTR_BACKUP_SOURCE_VERSION = "bk_src_version";
+ private static final String ATTR_BACKUP_ALLOWED = "allow-backup";
+ private static final String ATTR_BACKUP_SOURCE_BACKUP_ALLOWED = "bk_src_backup-allowed";
private static final String ATTR_SHADOW = "shadow";
private static final String TAG_SIGNATURE = "signature";
private static final String ATTR_SIGNATURE_HASH = "hash";
- private static final int VERSION_UNKNOWN = -1;
-
/**
* When true, this package information was restored from the previous device, and the app hasn't
* been installed yet.
*/
private boolean mIsShadow;
- private int mVersionCode = VERSION_UNKNOWN;
+ private int mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
+ private int mBackupSourceVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
private long mLastUpdateTime;
private ArrayList<byte[]> mSigHashes;
+ // mBackupAllowed didn't used to be parsisted, so we don't restore it from a file.
+ // mBackupAllowed will always start with false, and will have been updated before making a
+ // backup next time, which works file.
+ // We just don't want to print an uninitialzied mBackupAlldowed value on dumpsys, so
+ // we use this boolean to control dumpsys.
+ private boolean mBackupAllowedInitialized;
+ private boolean mBackupAllowed;
+ private boolean mBackupSourceBackupAllowed;
+
private ShortcutPackageInfo(int versionCode, long lastUpdateTime,
ArrayList<byte[]> sigHashes, boolean isShadow) {
mVersionCode = versionCode;
mLastUpdateTime = lastUpdateTime;
mIsShadow = isShadow;
mSigHashes = sigHashes;
+ mBackupAllowed = false; // By default, we assume false.
+ mBackupSourceBackupAllowed = false;
}
public static ShortcutPackageInfo newEmpty() {
- return new ShortcutPackageInfo(VERSION_UNKNOWN, /* last update time =*/ 0,
+ return new ShortcutPackageInfo(ShortcutInfo.VERSION_CODE_UNKNOWN, /* last update time =*/ 0,
new ArrayList<>(0), /* isShadow */ false);
}
@@ -86,15 +100,33 @@ class ShortcutPackageInfo {
return mVersionCode;
}
+ public int getBackupSourceVersionCode() {
+ return mBackupSourceVersionCode;
+ }
+
+ @VisibleForTesting
+ public boolean isBackupSourceBackupAllowed() {
+ return mBackupSourceBackupAllowed;
+ }
+
public long getLastUpdateTime() {
return mLastUpdateTime;
}
- /** Set {@link #mVersionCode} and {@link #mLastUpdateTime} from a {@link PackageInfo}. */
- public void updateVersionInfo(@NonNull PackageInfo pi) {
+ public boolean isBackupAllowed() {
+ return mBackupAllowed;
+ }
+
+ /**
+ * Set {@link #mVersionCode}, {@link #mLastUpdateTime} and {@link #mBackupAllowed}
+ * from a {@link PackageInfo}.
+ */
+ public void updateFromPackageInfo(@NonNull PackageInfo pi) {
if (pi != null) {
mVersionCode = pi.versionCode;
mLastUpdateTime = pi.lastUpdateTime;
+ mBackupAllowed = ShortcutService.shouldBackupApp(pi);
+ mBackupAllowedInitialized = true;
}
}
@@ -102,23 +134,24 @@ class ShortcutPackageInfo {
return mSigHashes.size() > 0;
}
- public boolean canRestoreTo(ShortcutService s, PackageInfo target) {
- if (!s.shouldBackupApp(target)) {
+ //@DisabledReason
+ public int canRestoreTo(ShortcutService s, PackageInfo currentPackage, boolean anyVersionOkay) {
+ if (!BackupUtils.signaturesMatch(mSigHashes, currentPackage)) {
+ Slog.w(TAG, "Can't restore: Package signature mismatch");
+ return ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
+ }
+ if (!ShortcutService.shouldBackupApp(currentPackage) || !mBackupSourceBackupAllowed) {
// "allowBackup" was true when backed up, but now false.
- Slog.w(TAG, "Can't restore: package no longer allows backup");
- return false;
+ Slog.w(TAG, "Can't restore: package didn't or doesn't allow backup");
+ return ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED;
}
- if (target.versionCode < mVersionCode) {
+ if (!anyVersionOkay && (currentPackage.versionCode < mBackupSourceVersionCode)) {
Slog.w(TAG, String.format(
"Can't restore: package current version %d < backed up version %d",
- target.versionCode, mVersionCode));
- return false;
- }
- if (!BackupUtils.signaturesMatch(mSigHashes, target)) {
- Slog.w(TAG, "Can't restore: Package signature mismatch");
- return false;
+ currentPackage.versionCode, mBackupSourceVersionCode));
+ return ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
}
- return true;
+ return ShortcutInfo.DISABLED_REASON_NOT_DISABLED;
}
@VisibleForTesting
@@ -132,6 +165,8 @@ class ShortcutPackageInfo {
final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, pi.lastUpdateTime,
BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false);
+ ret.mBackupSourceBackupAllowed = s.shouldBackupApp(pi);
+ ret.mBackupSourceVersionCode = pi.versionCode;
return ret;
}
@@ -151,13 +186,19 @@ class ShortcutPackageInfo {
mSigHashes = BackupUtils.hashSignatureArray(pi.signatures);
}
- public void saveToXml(XmlSerializer out) throws IOException {
+ public void saveToXml(XmlSerializer out, boolean forBackup) throws IOException {
out.startTag(null, TAG_ROOT);
ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime);
ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
+ ShortcutService.writeAttr(out, ATTR_BACKUP_ALLOWED, mBackupAllowed);
+
+ ShortcutService.writeAttr(out, ATTR_BACKUP_SOURCE_VERSION, mBackupSourceVersionCode);
+ ShortcutService.writeAttr(out,
+ ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, mBackupSourceBackupAllowed);
+
for (int i = 0; i < mSigHashes.size(); i++) {
out.startTag(null, TAG_SIGNATURE);
@@ -171,7 +212,9 @@ class ShortcutPackageInfo {
public void loadFromXml(XmlPullParser parser, boolean fromBackup)
throws IOException, XmlPullParserException {
- final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
+ // Don't use the version code from the backup file.
+ final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION,
+ ShortcutInfo.VERSION_CODE_UNKNOWN);
final long lastUpdateTime = ShortcutService.parseLongAttribute(
parser, ATTR_LAST_UPDATE_TIME);
@@ -180,6 +223,20 @@ class ShortcutPackageInfo {
final boolean shadow =
fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
+ // We didn't used to save these attributes, and all backed up shortcuts were from
+ // apps that support backups, so the default values take this fact into consideration.
+ final int backupSourceVersion = ShortcutService.parseIntAttribute(parser,
+ ATTR_BACKUP_SOURCE_VERSION, ShortcutInfo.VERSION_CODE_UNKNOWN);
+
+ // Note the only time these "true" default value is used is when restoring from an old
+ // build that didn't save ATTR_BACKUP_ALLOWED, and that means all the data included in
+ // a backup file were from apps that support backup, so we can just use "true" as the
+ // default.
+ final boolean backupAllowed = ShortcutService.parseBooleanAttribute(
+ parser, ATTR_BACKUP_ALLOWED, true);
+ final boolean backupSourceBackupAllowed = ShortcutService.parseBooleanAttribute(
+ parser, ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, true);
+
final ArrayList<byte[]> hashes = new ArrayList<>();
final int outerDepth = parser.getDepth();
@@ -207,11 +264,28 @@ class ShortcutPackageInfo {
ShortcutService.warnForInvalidTag(depth, tag);
}
- // Successfully loaded; replace the feilds.
- mVersionCode = versionCode;
+ // Successfully loaded; replace the fields.
+ if (fromBackup) {
+ mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
+ mBackupSourceVersionCode = versionCode;
+ mBackupSourceBackupAllowed = backupAllowed;
+ } else {
+ mVersionCode = versionCode;
+ mBackupSourceVersionCode = backupSourceVersion;
+ mBackupSourceBackupAllowed = backupSourceBackupAllowed;
+ }
mLastUpdateTime = lastUpdateTime;
mIsShadow = shadow;
mSigHashes = hashes;
+
+ // Note we don't restore it from the file because it didn't used to be saved.
+ // We always start by assuming backup is disabled for the current package,
+ // and this field will have been updated before we actually create a backup, at the same
+ // time when we update the version code.
+ // Until then, the value of mBackupAllowed shouldn't matter, but we don't want to print
+ // a false flag on dumpsys, so set mBackupAllowedInitialized to false.
+ mBackupAllowed = false;
+ mBackupAllowedInitialized = false;
}
public void dump(PrintWriter pw, String prefix) {
@@ -223,6 +297,7 @@ class ShortcutPackageInfo {
pw.print(prefix);
pw.print(" IsShadow: ");
pw.print(mIsShadow);
+ pw.print(mIsShadow ? " (not installed)" : " (installed)");
pw.println();
pw.print(prefix);
@@ -230,6 +305,25 @@ class ShortcutPackageInfo {
pw.print(mVersionCode);
pw.println();
+ if (mBackupAllowedInitialized) {
+ pw.print(prefix);
+ pw.print(" Backup Allowed: ");
+ pw.print(mBackupAllowed);
+ pw.println();
+ }
+
+ if (mBackupSourceVersionCode != ShortcutInfo.VERSION_CODE_UNKNOWN) {
+ pw.print(prefix);
+ pw.print(" Backup source version: ");
+ pw.print(mBackupSourceVersionCode);
+ pw.println();
+
+ pw.print(prefix);
+ pw.print(" Backup source backup allowed: ");
+ pw.print(mBackupSourceBackupAllowed);
+ pw.println();
+ }
+
pw.print(prefix);
pw.print(" Last package update time: ");
pw.print(mLastUpdateTime);
diff --git a/com/android/server/pm/ShortcutPackageItem.java b/com/android/server/pm/ShortcutPackageItem.java
index e59d69f4..689099cf 100644
--- a/com/android/server/pm/ShortcutPackageItem.java
+++ b/com/android/server/pm/ShortcutPackageItem.java
@@ -17,6 +17,7 @@ package com.android.server.pm;
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
+import android.content.pm.ShortcutInfo;
import android.util.Slog;
import com.android.internal.util.Preconditions;
@@ -101,51 +102,42 @@ abstract class ShortcutPackageItem {
final ShortcutService s = mShortcutUser.mService;
if (!s.isPackageInstalled(mPackageName, mPackageUserId)) {
if (ShortcutService.DEBUG) {
- Slog.d(TAG, String.format("Package still not installed: %s user=%d",
+ Slog.d(TAG, String.format("Package still not installed: %s/u%d",
mPackageName, mPackageUserId));
}
return; // Not installed, no need to restore yet.
}
- boolean blockRestore = false;
+ int restoreBlockReason;
+ int currentVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
+
if (!mPackageInfo.hasSignatures()) {
- s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId
+ s.wtf("Attempted to restore package " + mPackageName + "/u" + mPackageUserId
+ " but signatures not found in the restore data.");
- blockRestore = true;
- }
- if (!blockRestore) {
+ restoreBlockReason = ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
+ } else {
final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
- if (!mPackageInfo.canRestoreTo(s, pi)) {
- // Package is now installed, but can't restore. Let the subclass do the cleanup.
- blockRestore = true;
- }
+ currentVersionCode = pi.versionCode;
+ restoreBlockReason = mPackageInfo.canRestoreTo(s, pi, canRestoreAnyVersion());
}
- if (blockRestore) {
- onRestoreBlocked();
- } else {
- if (ShortcutService.DEBUG) {
- Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName,
- mPackageUserId, getOwnerUserId()));
- }
- onRestored();
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format("Restoring package: %s/u%d (version=%d) %s for u%d",
+ mPackageName, mPackageUserId, currentVersionCode,
+ ShortcutInfo.getDisabledReasonDebugString(restoreBlockReason),
+ getOwnerUserId()));
}
+ onRestored(restoreBlockReason);
+
// Either way, it's no longer a shadow.
mPackageInfo.setShadow(false);
s.scheduleSaveUser(mPackageUserId);
}
- /**
- * Called when the new package can't be restored because it has a lower version number
- * or different signatures.
- */
- protected abstract void onRestoreBlocked();
+ protected abstract boolean canRestoreAnyVersion();
- /**
- * Called when the new package is successfully restored.
- */
- protected abstract void onRestored();
+ protected abstract void onRestored(int restoreBlockReason);
public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException;
diff --git a/com/android/server/pm/ShortcutParser.java b/com/android/server/pm/ShortcutParser.java
index 3cf4200b..866c46c6 100644
--- a/com/android/server/pm/ShortcutParser.java
+++ b/com/android/server/pm/ShortcutParser.java
@@ -337,6 +337,9 @@ public class ShortcutParser {
(enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
| ShortcutInfo.FLAG_IMMUTABLE
| ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0);
+ final int disabledReason =
+ enabled ? ShortcutInfo.DISABLED_REASON_NOT_DISABLED
+ : ShortcutInfo.DISABLED_REASON_BY_APP;
// Note we don't need to set resource names here yet. They'll be set when they're about
// to be published.
@@ -363,6 +366,7 @@ public class ShortcutParser {
flags,
iconResId,
null, // icon res name
- null); // bitmap path
+ null, // bitmap path
+ disabledReason);
}
}
diff --git a/com/android/server/pm/ShortcutRequestPinProcessor.java b/com/android/server/pm/ShortcutRequestPinProcessor.java
index 8a8128db..3e44de98 100644
--- a/com/android/server/pm/ShortcutRequestPinProcessor.java
+++ b/com/android/server/pm/ShortcutRequestPinProcessor.java
@@ -300,10 +300,12 @@ class ShortcutRequestPinProcessor {
final ShortcutInfo existing = ps.findShortcutById(inShortcut.getId());
final boolean existsAlready = existing != null;
+ final boolean existingIsVisible = existsAlready && existing.isVisibleToPublisher();
if (DEBUG) {
Slog.d(TAG, "requestPinnedShortcut: package=" + inShortcut.getPackage()
+ " existsAlready=" + existsAlready
+ + " existingIsVisible=" + existingIsVisible
+ " shortcut=" + inShortcut.toInsecureString());
}
@@ -378,7 +380,6 @@ class ShortcutRequestPinProcessor {
// manifest shortcut.)
Preconditions.checkArgument(shortcutInfo.isEnabled(),
"Shortcut ID=" + shortcutInfo + " already exists but disabled.");
-
}
private boolean startRequestConfirmActivity(ComponentName activity, int launcherUserId,
@@ -463,7 +464,7 @@ class ShortcutRequestPinProcessor {
launcher.attemptToRestoreIfNeededAndSave();
if (launcher.hasPinned(original)) {
if (DEBUG) {
- Slog.d(TAG, "Shortcut " + original + " already pinned.");
+ Slog.d(TAG, "Shortcut " + original + " already pinned."); // This too.
}
return true;
}
@@ -497,7 +498,7 @@ class ShortcutRequestPinProcessor {
if (original.getActivity() == null) {
original.setActivity(mService.getDummyMainActivity(appPackageName));
}
- ps.addOrUpdateDynamicShortcut(original);
+ ps.addOrReplaceDynamicShortcut(original);
}
// Pin the shortcut.
@@ -505,13 +506,14 @@ class ShortcutRequestPinProcessor {
Slog.d(TAG, "Pinning " + shortcutId);
}
- launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId);
+ launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId,
+ /*forPinRequest=*/ true);
if (current == null) {
if (DEBUG) {
Slog.d(TAG, "Removing " + shortcutId + " as dynamic");
}
- ps.deleteDynamicWithId(shortcutId);
+ ps.deleteDynamicWithId(shortcutId, /*ignoreInvisible=*/ false);
}
ps.adjustRanks(); // Shouldn't be needed, but just in case.
diff --git a/com/android/server/pm/ShortcutService.java b/com/android/server/pm/ShortcutService.java
index 27560c5f..1c002aa4 100644
--- a/com/android/server/pm/ShortcutService.java
+++ b/com/android/server/pm/ShortcutService.java
@@ -553,6 +553,9 @@ public class ShortcutService extends IShortcutService.Stub {
public Lifecycle(Context context) {
super(context);
+ if (DEBUG) {
+ Binder.LOG_RUNTIME_EXCEPTION = true;
+ }
mService = new ShortcutService(context);
}
@@ -738,6 +741,10 @@ public class ShortcutService extends IShortcutService.Stub {
return parseLongAttribute(parser, attribute) == 1;
}
+ static boolean parseBooleanAttribute(XmlPullParser parser, String attribute, boolean def) {
+ return parseLongAttribute(parser, attribute, (def ? 1 : 0)) == 1;
+ }
+
static int parseIntAttribute(XmlPullParser parser, String attribute) {
return (int) parseLongAttribute(parser, attribute);
}
@@ -835,6 +842,8 @@ public class ShortcutService extends IShortcutService.Stub {
static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException {
if (value) {
writeAttr(out, name, "1");
+ } else {
+ writeAttr(out, name, "0");
}
}
@@ -1689,7 +1698,7 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+ ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
fillInDefaultActivity(newShortcuts);
@@ -1709,12 +1718,12 @@ public class ShortcutService extends IShortcutService.Stub {
}
// First, remove all un-pinned; dynamic shortcuts
- ps.deleteAllDynamicShortcuts();
+ ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
// Then, add/update all. We need to make sure to take over "pinned" flag.
for (int i = 0; i < size; i++) {
final ShortcutInfo newShortcut = newShortcuts.get(i);
- ps.addOrUpdateDynamicShortcut(newShortcut);
+ ps.addOrReplaceDynamicShortcut(newShortcut);
}
// Lastly, adjust the ranks.
@@ -1740,7 +1749,7 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+ ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
// For update, don't fill in the default activity. Having null activity means
// "don't update the activity" here.
@@ -1761,7 +1770,9 @@ public class ShortcutService extends IShortcutService.Stub {
fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
final ShortcutInfo target = ps.findShortcutById(source.getId());
- if (target == null) {
+
+ // Invisible shortcuts can't be updated.
+ if (target == null || !target.isVisibleToPublisher()) {
continue;
}
@@ -1808,7 +1819,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
@Override
- public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
+ public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
@UserIdInt int userId) {
verifyCaller(packageName, userId);
@@ -1820,7 +1831,7 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+ ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
fillInDefaultActivity(newShortcuts);
@@ -1845,7 +1856,7 @@ public class ShortcutService extends IShortcutService.Stub {
newShortcut.setRankChanged();
// Add it.
- ps.addOrUpdateDynamicShortcut(newShortcut);
+ ps.addOrReplaceDynamicShortcut(newShortcut);
}
// Lastly, adjust the ranks.
@@ -1901,6 +1912,22 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkState(isUidForegroundLocked(injectBinderCallingUid()),
"Calling application must have a foreground activity or a foreground service");
+ // If it's a pin shortcut request, and there's already a shortcut with the same ID
+ // that's not visible to the caller (i.e. restore-blocked; meaning it's pinned by
+ // someone already), then we just replace the existing one with this new one,
+ // and then proceed the rest of the process.
+ if (shortcut != null) {
+ final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(
+ packageName, userId);
+ final String id = shortcut.getId();
+ if (ps.isShortcutExistsAndInvisibleToPublisher(id)) {
+
+ ps.updateInvisibleShortcutForPinRequestWith(shortcut);
+
+ packageShortcutsChanged(packageName, userId);
+ }
+ }
+
// Send request to the launcher, if supported.
ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, extras,
userId, resultIntent);
@@ -1922,15 +1949,21 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+ ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
+ /*ignoreInvisible=*/ true);
final String disabledMessageString =
(disabledMessage == null) ? null : disabledMessage.toString();
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
- ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)),
+ final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
+ if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
+ continue;
+ }
+ ps.disableWithId(id,
disabledMessageString, disabledMessageResId,
- /* overrideImmutable=*/ false);
+ /* overrideImmutable=*/ false, /*ignoreInvisible=*/ true,
+ ShortcutInfo.DISABLED_REASON_BY_APP);
}
// We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
@@ -1951,10 +1984,15 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+ ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
+ /*ignoreInvisible=*/ true);
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
- ps.enableWithId((String) shortcutIds.get(i));
+ final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
+ if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
+ continue;
+ }
+ ps.enableWithId(id);
}
}
packageShortcutsChanged(packageName, userId);
@@ -1973,11 +2011,15 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+ ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
+ /*ignoreInvisible=*/ true);
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
- ps.deleteDynamicWithId(
- Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
+ final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
+ if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
+ continue;
+ }
+ ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true);
}
// We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
@@ -1996,7 +2038,7 @@ public class ShortcutService extends IShortcutService.Stub {
throwIfUserLockedL(userId);
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.deleteAllDynamicShortcuts();
+ ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
}
packageShortcutsChanged(packageName, userId);
@@ -2013,7 +2055,7 @@ public class ShortcutService extends IShortcutService.Stub {
return getShortcutsWithQueryLocked(
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
- ShortcutInfo::isDynamic);
+ ShortcutInfo::isDynamicVisible);
}
}
@@ -2027,7 +2069,7 @@ public class ShortcutService extends IShortcutService.Stub {
return getShortcutsWithQueryLocked(
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
- ShortcutInfo::isManifestShortcut);
+ ShortcutInfo::isManifestVisible);
}
}
@@ -2041,7 +2083,7 @@ public class ShortcutService extends IShortcutService.Stub {
return getShortcutsWithQueryLocked(
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
- ShortcutInfo::isPinned);
+ ShortcutInfo::isPinnedVisible);
}
}
@@ -2190,7 +2232,11 @@ public class ShortcutService extends IShortcutService.Stub {
}
// We override this method in unit tests to do a simpler check.
- boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
+ boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId,
+ int callingPid, int callingUid) {
+ if (injectCheckAccessShortcutsPermission(callingPid, callingUid)) {
+ return true;
+ }
final long start = injectElapsedRealtime();
try {
return hasShortcutHostPermissionInner(callingPackage, userId);
@@ -2199,6 +2245,14 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
+ /**
+ * Returns true if the caller has the "ACCESS_SHORTCUTS" permission.
+ */
+ boolean injectCheckAccessShortcutsPermission(int callingPid, int callingUid) {
+ return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS,
+ callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+ }
+
// This method is extracted so we can directly call this method from unit tests,
// even when hasShortcutPermission() is overridden.
@VisibleForTesting
@@ -2379,7 +2433,7 @@ public class ShortcutService extends IShortcutService.Stub {
@NonNull String callingPackage, long changedSince,
@Nullable String packageName, @Nullable List<String> shortcutIds,
@Nullable ComponentName componentName,
- int queryFlags, int userId) {
+ int queryFlags, int userId, int callingPid, int callingUid) {
final ArrayList<ShortcutInfo> ret = new ArrayList<>();
final boolean cloneKeyFieldOnly =
@@ -2400,13 +2454,15 @@ public class ShortcutService extends IShortcutService.Stub {
if (packageName != null) {
getShortcutsInnerLocked(launcherUserId,
callingPackage, packageName, shortcutIds, changedSince,
- componentName, queryFlags, userId, ret, cloneFlag);
+ componentName, queryFlags, userId, ret, cloneFlag,
+ callingPid, callingUid);
} else {
final List<String> shortcutIdsF = shortcutIds;
getUserShortcutsLocked(userId).forAllPackages(p -> {
getShortcutsInnerLocked(launcherUserId,
callingPackage, p.getPackageName(), shortcutIdsF, changedSince,
- componentName, queryFlags, userId, ret, cloneFlag);
+ componentName, queryFlags, userId, ret, cloneFlag,
+ callingPid, callingUid);
});
}
}
@@ -2416,7 +2472,8 @@ public class ShortcutService extends IShortcutService.Stub {
private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
@Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince,
@Nullable ComponentName componentName, int queryFlags,
- int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
+ int userId, ArrayList<ShortcutInfo> ret, int cloneFlag,
+ int callingPid, int callingUid) {
final ArraySet<String> ids = shortcutIds == null ? null
: new ArraySet<>(shortcutIds);
@@ -2425,6 +2482,13 @@ public class ShortcutService extends IShortcutService.Stub {
if (p == null) {
return; // No need to instantiate ShortcutPackage.
}
+ final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0;
+ final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0;
+ final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0;
+
+ final boolean getPinnedByAnyLauncher =
+ ((queryFlags & ShortcutQuery.FLAG_MATCH_ALL_PINNED) != 0)
+ && injectCheckAccessShortcutsPermission(callingPid, callingUid);
p.findAll(ret,
(ShortcutInfo si) -> {
@@ -2440,20 +2504,17 @@ public class ShortcutService extends IShortcutService.Stub {
return false;
}
}
- if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
- && si.isDynamic()) {
+ if (matchDynamic && si.isDynamic()) {
return true;
}
- if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
- && si.isPinned()) {
+ if ((matchPinned && si.isPinned()) || getPinnedByAnyLauncher) {
return true;
}
- if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0)
- && si.isManifestShortcut()) {
+ if (matchManifest && si.isDeclaredInManifest()) {
return true;
}
return false;
- }, cloneFlag, callingPackage, launcherUserId);
+ }, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher);
}
@Override
@@ -2470,14 +2531,16 @@ public class ShortcutService extends IShortcutService.Stub {
.attemptToRestoreIfNeededAndSave();
final ShortcutInfo si = getShortcutInfoLocked(
- launcherUserId, callingPackage, packageName, shortcutId, userId);
+ launcherUserId, callingPackage, packageName, shortcutId, userId,
+ /*getPinnedByAnyLauncher=*/ false);
return si != null && si.isPinned();
}
}
private ShortcutInfo getShortcutInfoLocked(
int launcherUserId, @NonNull String callingPackage,
- @NonNull String packageName, @NonNull String shortcutId, int userId) {
+ @NonNull String packageName, @NonNull String shortcutId, int userId,
+ boolean getPinnedByAnyLauncher) {
Preconditions.checkStringNotEmpty(packageName, "packageName");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
@@ -2493,7 +2556,7 @@ public class ShortcutService extends IShortcutService.Stub {
final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
p.findAll(list,
(ShortcutInfo si) -> shortcutId.equals(si.getId()),
- /* clone flags=*/ 0, callingPackage, launcherUserId);
+ /* clone flags=*/ 0, callingPackage, launcherUserId, getPinnedByAnyLauncher);
return list.size() == 0 ? null : list.get(0);
}
@@ -2513,7 +2576,7 @@ public class ShortcutService extends IShortcutService.Stub {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
launcher.attemptToRestoreIfNeededAndSave();
- launcher.pinShortcuts(userId, packageName, shortcutIds);
+ launcher.pinShortcuts(userId, packageName, shortcutIds, /*forPinRequest=*/ false);
}
packageShortcutsChanged(packageName, userId);
@@ -2523,7 +2586,8 @@ public class ShortcutService extends IShortcutService.Stub {
@Override
public Intent[] createShortcutIntents(int launcherUserId,
@NonNull String callingPackage,
- @NonNull String packageName, @NonNull String shortcutId, int userId) {
+ @NonNull String packageName, @NonNull String shortcutId, int userId,
+ int callingPid, int callingUid) {
// Calling permission must be checked by LauncherAppsImpl.
Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
@@ -2535,9 +2599,13 @@ public class ShortcutService extends IShortcutService.Stub {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave();
+ final boolean getPinnedByAnyLauncher =
+ injectCheckAccessShortcutsPermission(callingPid, callingUid);
+
// Make sure the shortcut is actually visible to the launcher.
final ShortcutInfo si = getShortcutInfoLocked(
- launcherUserId, callingPackage, packageName, shortcutId, userId);
+ launcherUserId, callingPackage, packageName, shortcutId, userId,
+ getPinnedByAnyLauncher);
// "si == null" should suffice here, but check the flags too just to make sure.
if (si == null || !si.isEnabled() || !si.isAlive()) {
Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled");
@@ -2623,8 +2691,9 @@ public class ShortcutService extends IShortcutService.Stub {
@Override
public boolean hasShortcutHostPermission(int launcherUserId,
- @NonNull String callingPackage) {
- return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
+ @NonNull String callingPackage, int callingPid, int callingUid) {
+ return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId,
+ callingPid, callingUid);
}
@Override
@@ -3343,7 +3412,7 @@ public class ShortcutService extends IShortcutService.Stub {
return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
}
- boolean shouldBackupApp(PackageInfo pi) {
+ static boolean shouldBackupApp(PackageInfo pi) {
return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
}
@@ -3371,7 +3440,7 @@ public class ShortcutService extends IShortcutService.Stub {
// Set the version code for the launchers.
// We shouldn't do this for publisher packages, because we don't want to update the
// version code without rescanning the manifest.
- user.forAllLaunchers(launcher -> launcher.ensureVersionInfo());
+ user.forAllLaunchers(launcher -> launcher.ensurePackageInfo());
// Save to the filesystem.
scheduleSaveUser(userId);
diff --git a/com/android/server/pm/ShortcutUser.java b/com/android/server/pm/ShortcutUser.java
index 55e6d28a..48eccd02 100644
--- a/com/android/server/pm/ShortcutUser.java
+++ b/com/android/server/pm/ShortcutUser.java
@@ -364,9 +364,6 @@ class ShortcutUser {
private void saveShortcutPackageItem(XmlSerializer out,
ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
if (forBackup) {
- if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
- return; // Don't save.
- }
if (spi.getPackageUserId() != spi.getOwnerUserId()) {
return; // Don't save cross-user information.
}
diff --git a/com/android/server/pm/UserRestrictionsUtils.java b/com/android/server/pm/UserRestrictionsUtils.java
index a6b05d71..c18a71d3 100644
--- a/com/android/server/pm/UserRestrictionsUtils.java
+++ b/com/android/server/pm/UserRestrictionsUtils.java
@@ -96,6 +96,7 @@ public class UserRestrictionsUtils {
UserManager.DISALLOW_SMS,
UserManager.DISALLOW_FUN,
UserManager.DISALLOW_CREATE_WINDOWS,
+ UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
UserManager.DISALLOW_OUTGOING_BEAM,
UserManager.DISALLOW_WALLPAPER,
@@ -156,6 +157,7 @@ public class UserRestrictionsUtils {
private static final Set<String> GLOBAL_RESTRICTIONS = Sets.newArraySet(
UserManager.DISALLOW_ADJUST_VOLUME,
UserManager.DISALLOW_BLUETOOTH_SHARING,
+ UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
UserManager.DISALLOW_RUN_IN_BACKGROUND,
UserManager.DISALLOW_UNMUTE_MICROPHONE,
UserManager.DISALLOW_UNMUTE_DEVICE
diff --git a/com/android/server/pm/permission/BasePermission.java b/com/android/server/pm/permission/BasePermission.java
index 09a6e9c0..71d3202d 100644
--- a/com/android/server/pm/permission/BasePermission.java
+++ b/com/android/server/pm/permission/BasePermission.java
@@ -48,6 +48,7 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -77,7 +78,7 @@ public final class BasePermission {
final String name;
- @PermissionType final int type;
+ final @PermissionType int type;
String sourcePackageName;
@@ -252,12 +253,12 @@ public final class BasePermission {
return changed;
}
- public void updateDynamicPermission(Map<String, BasePermission> permissionTrees) {
+ public void updateDynamicPermission(Collection<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);
+ final BasePermission tree = findPermissionTree(permissionTrees, name);
if (tree != null && tree.perm != null) {
sourcePackageSetting = tree.sourcePackageSetting;
perm = new PackageParser.Permission(tree.perm.owner,
@@ -269,8 +270,8 @@ public final class BasePermission {
}
}
- public static BasePermission createOrUpdate(@Nullable BasePermission bp, @NonNull Permission p,
- @NonNull PackageParser.Package pkg, Map<String, BasePermission> permissionTrees,
+ static BasePermission createOrUpdate(@Nullable BasePermission bp, @NonNull Permission p,
+ @NonNull PackageParser.Package pkg, Collection<BasePermission> permissionTrees,
boolean chatty) {
final PackageSettingBase pkgSetting = (PackageSettingBase) pkg.mExtras;
// Allow system apps to redefine non-system permissions
@@ -300,7 +301,7 @@ public final class BasePermission {
if (bp.perm == null) {
if (bp.sourcePackageName == null
|| bp.sourcePackageName.equals(p.info.packageName)) {
- final BasePermission tree = findPermissionTreeLP(permissionTrees, p.info.name);
+ final BasePermission tree = findPermissionTree(permissionTrees, p.info.name);
if (tree == null
|| tree.sourcePackageName.equals(p.info.packageName)) {
bp.sourcePackageSetting = pkgSetting;
@@ -345,12 +346,12 @@ public final class BasePermission {
return bp;
}
- public static BasePermission enforcePermissionTreeLP(
- Map<String, BasePermission> permissionTrees, String permName, int callingUid) {
+ static BasePermission enforcePermissionTree(
+ Collection<BasePermission> permissionTrees, String permName, int callingUid) {
if (permName != null) {
- BasePermission bp = findPermissionTreeLP(permissionTrees, permName);
+ BasePermission bp = findPermissionTree(permissionTrees, permName);
if (bp != null) {
- if (bp.uid == UserHandle.getAppId(callingUid)) {//UserHandle.getAppId(Binder.getCallingUid())) {
+ if (bp.uid == UserHandle.getAppId(callingUid)) {
return bp;
}
throw new SecurityException("Calling uid " + callingUid
@@ -373,9 +374,9 @@ public final class BasePermission {
}
}
- private static BasePermission findPermissionTreeLP(
- Map<String, BasePermission> permissionTrees, String permName) {
- for (BasePermission bp : permissionTrees.values()) {
+ private static BasePermission findPermissionTree(
+ Collection<BasePermission> permissionTrees, String permName) {
+ for (BasePermission bp : permissionTrees) {
if (permName.startsWith(bp.name) &&
permName.length() > bp.name.length() &&
permName.charAt(bp.name.length()) == '.') {
diff --git a/com/android/server/pm/permission/PermissionManagerInternal.java b/com/android/server/pm/permission/PermissionManagerInternal.java
index 3b20b42b..8aac52ae 100644
--- a/com/android/server/pm/permission/PermissionManagerInternal.java
+++ b/com/android/server/pm/permission/PermissionManagerInternal.java
@@ -31,6 +31,7 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Internal interfaces to be used by other components within the system server.
@@ -81,11 +82,26 @@ public abstract class PermissionManagerInternal {
@NonNull int[] allUserIds);
- public abstract boolean addPermission(@NonNull PermissionInfo info, boolean async,
+ /**
+ * Add all permissions in the given package.
+ * <p>
+ * NOTE: argument {@code groupTEMP} is temporary until mPermissionGroups is moved to
+ * the permission settings.
+ */
+ public abstract void addAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty);
+ public abstract void removeAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty);
+ public abstract boolean addDynamicPermission(@NonNull PermissionInfo info, boolean async,
int callingUid, @Nullable PermissionCallback callback);
- public abstract void removePermission(@NonNull String permName, int callingUid,
+ public abstract void removeDynamicPermission(@NonNull String permName, int callingUid,
@Nullable PermissionCallback callback);
+ public abstract int updatePermissions(@Nullable String changingPkg,
+ @Nullable PackageParser.Package pkgInfo, int flags);
+ public abstract int updatePermissionTrees(@Nullable String changingPkg,
+ @Nullable PackageParser.Package pkgInfo, int flags);
+
+ public abstract @Nullable String[] getAppOpPermissionPackages(@NonNull String permName);
+
public abstract int getPermissionFlags(@NonNull String permName,
@NonNull String packageName, int callingUid, int userId);
/**
@@ -98,8 +114,6 @@ public abstract class PermissionManagerInternal {
*/
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
diff --git a/com/android/server/pm/permission/PermissionManagerService.java b/com/android/server/pm/permission/PermissionManagerService.java
index 6c031a6a..d2d857ca 100644
--- a/com/android/server/pm/permission/PermissionManagerService.java
+++ b/com/android/server/pm/permission/PermissionManagerService.java
@@ -69,6 +69,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
/**
* Manages all permissions and handles permissions related tasks.
@@ -260,7 +261,7 @@ public class PermissionManagerService {
// }
final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
- for (BasePermission bp : mSettings.getAllPermissionsLocked()) {
+ for (BasePermission bp : mSettings.mPermissions.values()) {
final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags);
if (pi != null) {
out.add(pi);
@@ -305,7 +306,98 @@ public class PermissionManagerService {
return protectionLevel;
}
- private boolean addPermission(
+ private void addAllPermissions(PackageParser.Package pkg, boolean chatty) {
+ final int N = pkg.permissions.size();
+ for (int i=0; i<N; i++) {
+ PackageParser.Permission p = pkg.permissions.get(i);
+
+ // Assume by default that we did not install this permission into the system.
+ p.info.flags &= ~PermissionInfo.FLAG_INSTALLED;
+
+ // Now that permission groups have a special meaning, we ignore permission
+ // groups for legacy apps to prevent unexpected behavior. In particular,
+ // permissions for one app being granted to someone just because they happen
+ // to be in a group defined by another app (before this had no implications).
+ if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+ p.group = mPackageManagerInt.getPermissionGroupTEMP(p.info.group);
+ // Warn for a permission in an unknown group.
+ if (PackageManagerService.DEBUG_PERMISSIONS
+ && p.info.group != null && p.group == null) {
+ Slog.i(TAG, "Permission " + p.info.name + " from package "
+ + p.info.packageName + " in an unknown group " + p.info.group);
+ }
+ }
+
+ synchronized (PermissionManagerService.this.mLock) {
+ if (p.tree) {
+ final BasePermission bp = BasePermission.createOrUpdate(
+ mSettings.getPermissionTreeLocked(p.info.name), p, pkg,
+ mSettings.getAllPermissionTreesLocked(), chatty);
+ mSettings.putPermissionTreeLocked(p.info.name, bp);
+ } else {
+ final BasePermission bp = BasePermission.createOrUpdate(
+ mSettings.getPermissionLocked(p.info.name),
+ p, pkg, mSettings.getAllPermissionTreesLocked(), chatty);
+ mSettings.putPermissionLocked(p.info.name, bp);
+ }
+ }
+ }
+ }
+
+ private void removeAllPermissions(PackageParser.Package pkg, boolean chatty) {
+ synchronized (mLock) {
+ int N = pkg.permissions.size();
+ StringBuilder r = null;
+ for (int i=0; i<N; i++) {
+ PackageParser.Permission p = pkg.permissions.get(i);
+ BasePermission bp = (BasePermission) mSettings.mPermissions.get(p.info.name);
+ if (bp == null) {
+ bp = mSettings.mPermissionTrees.get(p.info.name);
+ }
+ if (bp != null && bp.isPermission(p)) {
+ bp.setPermission(null);
+ if (PackageManagerService.DEBUG_REMOVE && chatty) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
+ }
+ r.append(p.info.name);
+ }
+ }
+ if (p.isAppOp()) {
+ ArraySet<String> appOpPkgs =
+ mSettings.mAppOpPermissionPackages.get(p.info.name);
+ if (appOpPkgs != null) {
+ appOpPkgs.remove(pkg.packageName);
+ }
+ }
+ }
+ if (r != null) {
+ if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r);
+ }
+
+ N = pkg.requestedPermissions.size();
+ r = null;
+ for (int i=0; i<N; i++) {
+ String perm = pkg.requestedPermissions.get(i);
+ if (mSettings.isPermissionAppOp(perm)) {
+ ArraySet<String> appOpPkgs = mSettings.mAppOpPermissionPackages.get(perm);
+ if (appOpPkgs != null) {
+ appOpPkgs.remove(pkg.packageName);
+ if (appOpPkgs.isEmpty()) {
+ mSettings.mAppOpPermissionPackages.remove(perm);
+ }
+ }
+ }
+ }
+ if (r != null) {
+ if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r);
+ }
+ }
+ }
+
+ private boolean addDynamicPermission(
PermissionInfo info, int callingUid, PermissionCallback callback) {
if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
throw new SecurityException("Instant apps can't add permissions");
@@ -313,8 +405,7 @@ public class PermissionManagerService {
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 BasePermission tree = mSettings.enforcePermissionTree(info.name, callingUid);
final boolean added;
final boolean changed;
synchronized (mLock) {
@@ -326,8 +417,8 @@ public class PermissionManagerService {
bp = new BasePermission(info.name, tree.getSourcePackageName(),
BasePermission.TYPE_DYNAMIC);
} else if (bp.isDynamic()) {
- throw new SecurityException(
- "Not allowed to modify non-dynamic permission "
+ // TODO: switch this back to SecurityException
+ Slog.wtf(TAG, "Not allowed to modify non-dynamic permission "
+ info.name);
}
changed = bp.addToTree(fixedLevel, info, tree);
@@ -341,21 +432,20 @@ public class PermissionManagerService {
return added;
}
- private void removePermission(
+ private void removeDynamicPermission(
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);
+ final BasePermission tree = mSettings.enforcePermissionTree(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 "
+ // TODO: switch this back to SecurityException
+ Slog.wtf(TAG, "Not allowed to modify non-dynamic permission "
+ permName);
}
mSettings.removePermissionLocked(permName);
@@ -713,7 +803,21 @@ public class PermissionManagerService {
return runtimePermissionChangedUserIds;
}
- private int getPermissionFlags(String permName, String packageName, int callingUid, int userId) {
+ private String[] getAppOpPermissionPackages(String permName) {
+ if (mPackageManagerInt.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+ return null;
+ }
+ synchronized (mLock) {
+ final ArraySet<String> pkgs = mSettings.mAppOpPermissionPackages.get(permName);
+ if (pkgs == null) {
+ return null;
+ }
+ return pkgs.toArray(new String[pkgs.size()]);
+ }
+ }
+
+ private int getPermissionFlags(
+ String permName, String packageName, int callingUid, int userId) {
if (!mUserManagerInt.exists(userId)) {
return 0;
}
@@ -741,6 +845,96 @@ public class PermissionManagerService {
return permissionsState.getPermissionFlags(permName, userId);
}
+ private int updatePermissions(String packageName, PackageParser.Package pkgInfo, int flags) {
+ Set<BasePermission> needsUpdate = null;
+ synchronized (mLock) {
+ final Iterator<BasePermission> it = mSettings.mPermissions.values().iterator();
+ while (it.hasNext()) {
+ final BasePermission bp = it.next();
+ if (bp.isDynamic()) {
+ bp.updateDynamicPermission(mSettings.mPermissionTrees.values());
+ }
+ if (bp.getSourcePackageSetting() != null) {
+ if (packageName != null && packageName.equals(bp.getSourcePackageName())
+ && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) {
+ Slog.i(TAG, "Removing old permission tree: " + bp.getName()
+ + " from package " + bp.getSourcePackageName());
+ flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL;
+ it.remove();
+ }
+ continue;
+ }
+ if (needsUpdate == null) {
+ needsUpdate = new ArraySet<>(mSettings.mPermissions.size());
+ }
+ needsUpdate.add(bp);
+ }
+ }
+ if (needsUpdate != null) {
+ for (final BasePermission bp : needsUpdate) {
+ final PackageParser.Package pkg =
+ mPackageManagerInt.getPackage(bp.getSourcePackageName());
+ synchronized (mLock) {
+ if (pkg != null && pkg.mExtras != null) {
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (bp.getSourcePackageSetting() == null) {
+ bp.setSourcePackageSetting(ps);
+ }
+ continue;
+ }
+ Slog.w(TAG, "Removing dangling permission: " + bp.getName()
+ + " from package " + bp.getSourcePackageName());
+ mSettings.removePermissionLocked(bp.getName());
+ }
+ }
+ }
+ return flags;
+ }
+
+ private int updatePermissionTrees(String packageName, PackageParser.Package pkgInfo,
+ int flags) {
+ Set<BasePermission> needsUpdate = null;
+ synchronized (mLock) {
+ final Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator();
+ while (it.hasNext()) {
+ final BasePermission bp = it.next();
+ if (bp.getSourcePackageSetting() != null) {
+ if (packageName != null && packageName.equals(bp.getSourcePackageName())
+ && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) {
+ Slog.i(TAG, "Removing old permission tree: " + bp.getName()
+ + " from package " + bp.getSourcePackageName());
+ flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL;
+ it.remove();
+ }
+ continue;
+ }
+ if (needsUpdate == null) {
+ needsUpdate = new ArraySet<>(mSettings.mPermissionTrees.size());
+ }
+ needsUpdate.add(bp);
+ }
+ }
+ if (needsUpdate != null) {
+ for (final BasePermission bp : needsUpdate) {
+ final PackageParser.Package pkg =
+ mPackageManagerInt.getPackage(bp.getSourcePackageName());
+ synchronized (mLock) {
+ if (pkg != null && pkg.mExtras != null) {
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (bp.getSourcePackageSetting() == null) {
+ bp.setSourcePackageSetting(ps);
+ }
+ continue;
+ }
+ Slog.w(TAG, "Removing dangling permission tree: " + bp.getName()
+ + " from package " + bp.getSourcePackageName());
+ mSettings.removePermissionLocked(bp.getName());
+ }
+ }
+ }
+ return flags;
+ }
+
private void updatePermissionFlags(String permName, String packageName, int flagMask,
int flagValues, int callingUid, int userId, PermissionCallback callback) {
if (!mUserManagerInt.exists(userId)) {
@@ -872,7 +1066,7 @@ public class PermissionManagerService {
private int calculateCurrentPermissionFootprintLocked(BasePermission tree) {
int size = 0;
- for (BasePermission perm : mSettings.getAllPermissionsLocked()) {
+ for (BasePermission perm : mSettings.mPermissions.values()) {
size += tree.calculateFootprint(perm);
}
return size;
@@ -889,6 +1083,15 @@ public class PermissionManagerService {
}
}
+ private static boolean hasPermission(PackageParser.Package pkgInfo, String permName) {
+ for (int i=pkgInfo.permissions.size()-1; i>=0; i--) {
+ if (pkgInfo.permissions.get(i).info.name.equals(permName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Get the first event id for the permission.
*
@@ -951,14 +1154,22 @@ public class PermissionManagerService {
private class PermissionManagerInternalImpl extends PermissionManagerInternal {
@Override
- public boolean addPermission(PermissionInfo info, boolean async, int callingUid,
+ public void addAllPermissions(Package pkg, boolean chatty) {
+ PermissionManagerService.this.addAllPermissions(pkg, chatty);
+ }
+ @Override
+ public void removeAllPermissions(Package pkg, boolean chatty) {
+ PermissionManagerService.this.removeAllPermissions(pkg, chatty);
+ }
+ @Override
+ public boolean addDynamicPermission(PermissionInfo info, boolean async, int callingUid,
PermissionCallback callback) {
- return PermissionManagerService.this.addPermission(info, callingUid, callback);
+ return PermissionManagerService.this.addDynamicPermission(info, callingUid, callback);
}
@Override
- public void removePermission(String permName, int callingUid,
+ public void removeDynamicPermission(String permName, int callingUid,
PermissionCallback callback) {
- PermissionManagerService.this.removePermission(permName, callingUid, callback);
+ PermissionManagerService.this.removeDynamicPermission(permName, callingUid, callback);
}
@Override
public void grantRuntimePermission(String permName, String packageName,
@@ -993,12 +1204,26 @@ public class PermissionManagerService {
(SharedUserSetting) suSetting, allUserIds);
}
@Override
+ public String[] getAppOpPermissionPackages(String permName) {
+ return PermissionManagerService.this.getAppOpPermissionPackages(permName);
+ }
+ @Override
public int getPermissionFlags(String permName, String packageName, int callingUid,
int userId) {
return PermissionManagerService.this.getPermissionFlags(permName, packageName,
callingUid, userId);
}
@Override
+ public int updatePermissions(String packageName,
+ PackageParser.Package pkgInfo, int flags) {
+ return PermissionManagerService.this.updatePermissions(packageName, pkgInfo, flags);
+ }
+ @Override
+ public int updatePermissionTrees(String packageName,
+ PackageParser.Package pkgInfo, int flags) {
+ return PermissionManagerService.this.updatePermissionTrees(packageName, pkgInfo, flags);
+ }
+ @Override
public void updatePermissionFlags(String permName, String packageName, int flagMask,
int flagValues, int callingUid, int userId, PermissionCallback callback) {
PermissionManagerService.this.updatePermissionFlags(
@@ -1038,20 +1263,6 @@ public class PermissionManagerService {
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;
}
diff --git a/com/android/server/pm/permission/PermissionSettings.java b/com/android/server/pm/permission/PermissionSettings.java
index 7a2e5ecc..7d125c9e 100644
--- a/com/android/server/pm/permission/PermissionSettings.java
+++ b/com/android/server/pm/permission/PermissionSettings.java
@@ -24,6 +24,7 @@ import android.util.ArraySet;
import android.util.Log;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.XmlUtils;
import com.android.server.pm.DumpState;
import com.android.server.pm.PackageManagerService;
@@ -35,6 +36,7 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
+import java.util.Set;
/**
* Permissions and other related data. This class is not meant for
@@ -49,8 +51,25 @@ public class PermissionSettings {
* All of the permissions known to the system. The mapping is from permission
* name to permission object.
*/
- private final ArrayMap<String, BasePermission> mPermissions =
+ @GuardedBy("mLock")
+ final ArrayMap<String, BasePermission> mPermissions =
new ArrayMap<String, BasePermission>();
+
+ /**
+ * All permission trees known to the system. The mapping is from permission tree
+ * name to permission object.
+ */
+ @GuardedBy("mLock")
+ final ArrayMap<String, BasePermission> mPermissionTrees =
+ new ArrayMap<String, BasePermission>();
+
+ /**
+ * Set of packages that request a particular app op. The mapping is from permission
+ * name to package names.
+ */
+ @GuardedBy("mLock")
+ final ArrayMap<String, ArraySet<String>> mAppOpPermissionPackages = new ArrayMap<>();
+
private final Object mLock;
PermissionSettings(@NonNull Context context, @NonNull Object lock) {
@@ -65,15 +84,23 @@ public class PermissionSettings {
}
}
+ public void addAppOpPackage(String permName, String packageName) {
+ ArraySet<String> pkgs = mAppOpPermissionPackages.get(permName);
+ if (pkgs == null) {
+ pkgs = new ArraySet<>();
+ mAppOpPermissionPackages.put(permName, pkgs);
+ }
+ pkgs.add(packageName);
+ }
+
/**
* Transfers ownership of permissions from one package to another.
*/
- public void transferPermissions(String origPackageName, String newPackageName,
- ArrayMap<String, BasePermission> permissionTrees) {
+ public void transferPermissions(String origPackageName, String newPackageName) {
synchronized (mLock) {
for (int i=0; i<2; i++) {
ArrayMap<String, BasePermission> permissions =
- i == 0 ? permissionTrees : mPermissions;
+ i == 0 ? mPermissionTrees : mPermissions;
for (BasePermission bp : permissions.values()) {
bp.transfer(origPackageName, newPackageName);
}
@@ -94,9 +121,26 @@ public class PermissionSettings {
}
}
+ public void readPermissionTrees(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ synchronized (mLock) {
+ readPermissions(mPermissionTrees, parser);
+ }
+ }
+
public void writePermissions(XmlSerializer serializer) throws IOException {
- for (BasePermission bp : mPermissions.values()) {
- bp.writeLPr(serializer);
+ synchronized (mLock) {
+ for (BasePermission bp : mPermissions.values()) {
+ bp.writeLPr(serializer);
+ }
+ }
+ }
+
+ public void writePermissionTrees(XmlSerializer serializer) throws IOException {
+ synchronized (mLock) {
+ for (BasePermission bp : mPermissionTrees.values()) {
+ bp.writeLPr(serializer);
+ }
}
}
@@ -128,6 +172,22 @@ public class PermissionSettings {
printedSomething = bp.dumpPermissionsLPr(pw, packageName, permissionNames,
externalStorageEnforced, printedSomething, dumpState);
}
+ if (packageName == null && permissionNames == null) {
+ for (int iperm = 0; iperm<mAppOpPermissionPackages.size(); iperm++) {
+ if (iperm == 0) {
+ if (dumpState.onTitlePrinted())
+ pw.println();
+ pw.println("AppOp Permissions:");
+ }
+ pw.print(" AppOp Permission ");
+ pw.print(mAppOpPermissionPackages.keyAt(iperm));
+ pw.println(":");
+ ArraySet<String> pkgs = mAppOpPermissionPackages.valueAt(iperm);
+ for (int ipkg=0; ipkg<pkgs.size(); ipkg++) {
+ pw.print(" "); pw.println(pkgs.valueAt(ipkg));
+ }
+ }
+ }
}
}
@@ -135,15 +195,58 @@ public class PermissionSettings {
return mPermissions.get(permName);
}
+ @Nullable BasePermission getPermissionTreeLocked(@NonNull String permName) {
+ return mPermissionTrees.get(permName);
+ }
+
void putPermissionLocked(@NonNull String permName, @NonNull BasePermission permission) {
mPermissions.put(permName, permission);
}
+ void putPermissionTreeLocked(@NonNull String permName, @NonNull BasePermission permission) {
+ mPermissionTrees.put(permName, permission);
+ }
+
void removePermissionLocked(@NonNull String permName) {
mPermissions.remove(permName);
}
- Collection<BasePermission> getAllPermissionsLocked() {
+ void removePermissionTreeLocked(@NonNull String permName) {
+ mPermissionTrees.remove(permName);
+ }
+
+ @NonNull Collection<BasePermission> getAllPermissionsLocked() {
return mPermissions.values();
}
+
+ @NonNull Collection<BasePermission> getAllPermissionTreesLocked() {
+ return mPermissionTrees.values();
+ }
+
+ /**
+ * Returns the permission tree for the given permission.
+ * @throws SecurityException If the calling UID is not allowed to add permissions to the
+ * found permission tree.
+ */
+ @Nullable BasePermission enforcePermissionTree(@NonNull String permName, int callingUid) {
+ synchronized (mLock) {
+ return BasePermission.enforcePermissionTree(
+ mPermissionTrees.values(), permName, callingUid);
+ }
+ }
+
+ public boolean isPermissionInstant(String permName) {
+ synchronized (mLock) {
+ final BasePermission bp = mPermissions.get(permName);
+ return (bp != null && bp.isInstant());
+ }
+ }
+
+ boolean isPermissionAppOp(String permName) {
+ synchronized (mLock) {
+ final BasePermission bp = mPermissions.get(permName);
+ return (bp != null && bp.isAppOp());
+ }
+ }
+
}
diff --git a/com/android/server/policy/GlobalActions.java b/com/android/server/policy/GlobalActions.java
index 342ec4b7..7a2e630c 100644
--- a/com/android/server/policy/GlobalActions.java
+++ b/com/android/server/policy/GlobalActions.java
@@ -58,6 +58,9 @@ class GlobalActions implements GlobalActionsListener {
public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
+ if (mStatusBarInternal.isGlobalActionsDisabled()) {
+ return;
+ }
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = deviceProvisioned;
mShowing = true;
diff --git a/com/android/server/policy/PhoneWindowManager.java b/com/android/server/policy/PhoneWindowManager.java
index db7817ec..ceb0ad07 100644
--- a/com/android/server/policy/PhoneWindowManager.java
+++ b/com/android/server/policy/PhoneWindowManager.java
@@ -18,13 +18,14 @@ package com.android.server.policy;
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.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_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.Context.CONTEXT_RESTRICTED;
import static android.content.Context.DISPLAY_SERVICE;
@@ -552,7 +553,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
int mUserRotation = Surface.ROTATION_0;
- boolean mAccelerometerDefault;
boolean mSupportAutoRotation;
int mAllowAllRotations = -1;
@@ -707,7 +707,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Intent mVrHeadsetHomeIntent;
boolean mSearchKeyShortcutPending;
boolean mConsumeSearchKeyUp;
- boolean mAssistKeyLongPressed;
boolean mPendingMetaAction;
boolean mPendingCapsLockToggle;
int mMetaState;
@@ -838,6 +837,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private static final int MSG_DISPATCH_BACK_KEY_TO_AUTOFILL = 24;
private static final int MSG_SYSTEM_KEY_PRESS = 25;
private static final int MSG_HANDLE_ALL_APPS = 26;
+ private static final int MSG_LAUNCH_ASSIST = 27;
+ private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 28;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -879,8 +880,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case MSG_HIDE_BOOT_MESSAGE:
handleHideBootMessage();
break;
+ case MSG_LAUNCH_ASSIST:
+ final int deviceId = msg.arg1;
+ final String hint = (String) msg.obj;
+ launchAssistAction(hint, deviceId);
+ break;
+ case MSG_LAUNCH_ASSIST_LONG_PRESS:
+ launchAssistLongPressAction();
+ break;
case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK:
- launchVoiceAssistWithWakeLock(msg.arg1 != 0);
+ launchVoiceAssistWithWakeLock();
break;
case MSG_POWER_DELAYED_PRESS:
powerPress((Long)msg.obj, msg.arg1 != 0, msg.arg2);
@@ -910,7 +919,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
disposeInputConsumer((InputConsumer) msg.obj);
break;
case MSG_BACK_DELAYED_PRESS:
- backMultiPressAction((Long) msg.obj, msg.arg1);
+ backMultiPressAction(msg.arg1);
finishBackKeyPress();
break;
case MSG_ACCESSIBILITY_SHORTCUT:
@@ -1414,7 +1423,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
- private void backMultiPressAction(long eventTime, int count) {
+ private void backMultiPressAction(int count) {
if (count >= PANIC_PRESS_BACK_COUNT) {
switch (mPanicPressOnBackBehavior) {
case PANIC_PRESS_BACK_NOTHING:
@@ -1583,7 +1592,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
- private void sleepPress(long eventTime) {
+ private void sleepPress() {
if (mShortPressOnSleepBehavior == SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME) {
launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/);
}
@@ -2270,7 +2279,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// Only force the default orientation if the screen is xlarge, at least 960dp x 720dp, per
// http://developer.android.com/guide/practices/screens_support.html#range
- mForceDefaultOrientation = longSizeDp >= 960 && shortSizeDp >= 720 &&
+ // For car, ignore the dp limitation. It's physically impossible to rotate the car's screen
+ // so if the orientation is forced, we need to respect that no matter what.
+ boolean isCar = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE);
+ mForceDefaultOrientation = ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar) &&
res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation) &&
// For debug purposes the next line turns this feature off with:
// $ adb shell setprop config.override_forced_orient true
@@ -2844,7 +2857,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
boolean keyguardLocked = isKeyguardLocked();
boolean hideDockDivider = attrs.type == TYPE_DOCK_DIVIDER
- && !mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID);
+ && !mWindowManagerInternal.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
return (keyguardLocked && !allowWhenLocked && win.getDisplayId() == DEFAULT_DISPLAY)
|| hideDockDivider;
}
@@ -3537,44 +3550,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
toggleKeyboardShortcutsMenu(event.getDeviceId());
}
} else if (keyCode == KeyEvent.KEYCODE_ASSIST) {
- if (down) {
- if (repeatCount == 0) {
- mAssistKeyLongPressed = false;
- } else if (repeatCount == 1) {
- mAssistKeyLongPressed = true;
- if (!keyguardOn) {
- launchAssistLongPressAction();
- }
- }
- } else {
- if (mAssistKeyLongPressed) {
- mAssistKeyLongPressed = false;
- } else {
- if (!keyguardOn) {
- launchAssistAction(null, event.getDeviceId());
- }
- }
- }
+ Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
return -1;
} else if (keyCode == KeyEvent.KEYCODE_VOICE_ASSIST) {
- if (!down) {
- Intent voiceIntent;
- if (!keyguardOn) {
- voiceIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
- } else {
- IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- if (dic != null) {
- try {
- dic.exitIdle("voice-search");
- } catch (RemoteException e) {
- }
- }
- voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true);
- }
- startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF);
- }
+ Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in interceptKeyBeforeQueueing");
+ return -1;
} else if (keyCode == KeyEvent.KEYCODE_SYSRQ) {
if (down && repeatCount == 0) {
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
@@ -5445,7 +5425,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
&& attrs.type < FIRST_SYSTEM_WINDOW;
- final int stackId = win.getStackId();
+ final int windowingMode = win.getWindowingMode();
+ final boolean inFullScreenOrSplitScreenSecondaryWindowingMode =
+ windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi) {
if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) {
mForceStatusBar = true;
@@ -5464,7 +5447,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// represent should be hidden or if we should hide the lockscreen. For attached app
// windows we defer the decision to the window it is attached to.
if (appWindow && attached == null) {
- if (attrs.isFullscreen() && StackId.normallyFullscreenWindows(stackId)) {
+ if (attrs.isFullscreen() && inFullScreenOrSplitScreenSecondaryWindowingMode) {
if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win);
mTopFullscreenOpaqueWindowState = win;
if (mTopFullscreenOpaqueOrDimmingWindowState == null) {
@@ -5495,7 +5478,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// Keep track of the window if it's dimming but not necessarily fullscreen.
if (mTopFullscreenOpaqueOrDimmingWindowState == null && affectsSystemUi
- && win.isDimming() && StackId.normallyFullscreenWindows(stackId)) {
+ && win.isDimming() && inFullScreenOrSplitScreenSecondaryWindowingMode) {
mTopFullscreenOpaqueOrDimmingWindowState = win;
}
@@ -5503,7 +5486,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// separately, because both the "real fullscreen" opaque window and the one for the docked
// stack can control View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.
if (mTopDockedOpaqueWindowState == null && affectsSystemUi && appWindow && attached == null
- && attrs.isFullscreen() && stackId == DOCKED_STACK_ID) {
+ && attrs.isFullscreen() && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
mTopDockedOpaqueWindowState = win;
if (mTopDockedOpaqueOrDimmingWindowState == null) {
mTopDockedOpaqueOrDimmingWindowState = win;
@@ -5513,7 +5496,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// Also keep track of any windows that are dimming but not necessarily fullscreen in the
// docked stack.
if (mTopDockedOpaqueOrDimmingWindowState == null && affectsSystemUi && win.isDimming()
- && stackId == DOCKED_STACK_ID) {
+ && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
mTopDockedOpaqueOrDimmingWindowState = win;
}
@@ -5608,8 +5591,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
changes |= FINISH_LAYOUT_REDO_LAYOUT;
}
} else if (topIsFullscreen
- && !mWindowManagerInternal.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)
- && !mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID)) {
+ && !mWindowManagerInternal.isStackVisible(WINDOWING_MODE_FREEFORM)
+ && !mWindowManagerInternal.isStackVisible(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar");
if (mStatusBarController.setBarShowingLw(false)) {
changes |= FINISH_LAYOUT_REDO_LAYOUT;
@@ -6191,7 +6175,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
useHapticFeedback = false; // suppress feedback if already non-interactive
}
if (down) {
- sleepPress(event.getEventTime());
+ sleepPress();
} else {
sleepRelease(event.getEventTime());
}
@@ -6262,18 +6246,30 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
break;
}
+ case KeyEvent.KEYCODE_ASSIST: {
+ final boolean longPressed = event.getRepeatCount() > 0;
+ if (down && longPressed) {
+ Message msg = mHandler.obtainMessage(MSG_LAUNCH_ASSIST_LONG_PRESS);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ if (!down && !longPressed) {
+ Message msg = mHandler.obtainMessage(MSG_LAUNCH_ASSIST, event.getDeviceId(),
+ 0 /* unused */, null /* hint */);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ result &= ~ACTION_PASS_TO_USER;
+ break;
+ }
case KeyEvent.KEYCODE_VOICE_ASSIST: {
- // Only do this if we would otherwise not pass it to the user. In that case,
- // interceptKeyBeforeDispatching would apply a similar but different policy in
- // order to invoke voice assist actions. Note that we need to make a copy of the
- // key event here because the original key event will be recycled when we return.
- if ((result & ACTION_PASS_TO_USER) == 0 && !down) {
+ if (!down) {
mBroadcastWakeLock.acquire();
- Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK,
- keyguardActive ? 1 : 0, 0);
+ Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK);
msg.setAsynchronous(true);
msg.sendToTarget();
}
+ result &= ~ACTION_PASS_TO_USER;
break;
}
case KeyEvent.KEYCODE_WINDOW: {
@@ -6542,18 +6538,22 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
- void launchVoiceAssistWithWakeLock(boolean keyguardActive) {
- IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- if (dic != null) {
- try {
- dic.exitIdle("voice-search");
- } catch (RemoteException e) {
+ void launchVoiceAssistWithWakeLock() {
+ final Intent voiceIntent;
+ if (!keyguardOn()) {
+ voiceIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ } else {
+ IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+ if (dic != null) {
+ try {
+ dic.exitIdle("voice-search");
+ } catch (RemoteException e) {
+ }
}
+ voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true);
}
- Intent voiceIntent =
- new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, keyguardActive);
startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF);
mBroadcastWakeLock.release();
}
@@ -8009,9 +8009,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private int updateSystemBarsLw(WindowState win, int oldVis, int vis) {
- final boolean dockedStackVisible = mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID);
+ final boolean dockedStackVisible =
+ mWindowManagerInternal.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final boolean freeformStackVisible =
- mWindowManagerInternal.isStackVisible(FREEFORM_WORKSPACE_STACK_ID);
+ mWindowManagerInternal.isStackVisible(WINDOWING_MODE_FREEFORM);
final boolean resizing = mWindowManagerInternal.isDockedDividerResizing();
// We need to force system bars when the docked stack is visible, when the freeform stack
diff --git a/com/android/server/stats/StatsCompanionService.java b/com/android/server/stats/StatsCompanionService.java
index f1fb3e7b..ca3dd058 100644
--- a/com/android/server/stats/StatsCompanionService.java
+++ b/com/android/server/stats/StatsCompanionService.java
@@ -20,15 +20,25 @@ import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.IntentFilter;
import android.os.Binder;
+import android.os.Bundle;
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.os.UserHandle;
+import android.os.UserManager;
import android.util.Slog;
+import java.util.ArrayList;
+import java.util.List;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.KernelWakelockReader;
import com.android.internal.os.KernelWakelockStats;
@@ -53,6 +63,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
private final PendingIntent mAnomalyAlarmIntent;
private final PendingIntent mPollingAlarmIntent;
+ private final BroadcastReceiver mAppUpdateReceiver;
+ private final BroadcastReceiver mUserUpdateReceiver;
public StatsCompanionService(Context context) {
super();
@@ -63,8 +75,113 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
new Intent(mContext, AnomalyAlarmReceiver.class), 0);
mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
new Intent(mContext, PollingAlarmReceiver.class), 0);
+ mAppUpdateReceiver = new AppUpdateReceiver();
+ mUserUpdateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (sStatsdLock) {
+ sStatsd = fetchStatsdService();
+ if (sStatsd == null) {
+ Slog.w(TAG, "Could not access statsd");
+ return;
+ }
+ try {
+ // Pull the latest state of UID->app name, version mapping.
+ // Needed since the new user basically has a version of every app.
+ informAllUidsLocked(context);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
+ forgetEverything();
+ }
+ }
+ }
+ };
+ Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED.");
}
+ private final static int[] toIntArray(List<Integer> list){
+ int[] ret = new int[list.size()];
+ for(int i = 0;i < ret.length;i++) {
+ ret[i] = list.get(i);
+ }
+ return ret;
+ }
+
+ // Assumes that sStatsdLock is held.
+ private final void informAllUidsLocked(Context context) throws RemoteException {
+ UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ PackageManager pm = context.getPackageManager();
+ final List<UserInfo> users = um.getUsers(true);
+ if (DEBUG) {
+ Slog.w(TAG, "Iterating over "+users.size() + " profiles.");
+ }
+
+ List<Integer> uids = new ArrayList();
+ List<Integer> versions = new ArrayList();
+ List<String> apps = new ArrayList();
+
+ // Add in all the apps for every user/profile.
+ for (UserInfo profile : users) {
+ List<PackageInfo> pi = pm.getInstalledPackagesAsUser(0, profile.id);
+ for (int j = 0; j < pi.size(); j++) {
+ if (pi.get(j).applicationInfo != null) {
+ uids.add(pi.get(j).applicationInfo.uid);
+ versions.add(pi.get(j).versionCode);
+ apps.add(pi.get(j).packageName);
+ }
+ }
+ }
+ sStatsd.informAllUidData(toIntArray(uids), toIntArray(versions), apps.toArray(new
+ String[apps.size()]));
+ if (DEBUG) {
+ Slog.w(TAG, "Sent data for "+uids.size() +" apps");
+ }
+ }
+
+ public final static class AppUpdateReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
+ /**
+ * App updates actually consist of REMOVE, ADD, and then REPLACE broadcasts. To avoid
+ * waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag.
+ */
+ if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) &&
+ intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ return; // Keep only replacing or normal add and remove.
+ }
+ synchronized (sStatsdLock) {
+ if (sStatsd == null) {
+ Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
+ return;
+ }
+ try {
+ if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)) {
+ Bundle b = intent.getExtras();
+ int uid = b.getInt(Intent.EXTRA_UID);
+ boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ if (!replacing) {
+ // Don't bother sending an update if we're right about to get another
+ // intent for the new version that's added.
+ PackageManager pm = context.getPackageManager();
+ String app = intent.getData().getSchemeSpecificPart();
+ sStatsd.informOnePackageRemoved(app, uid);
+ }
+ } else {
+ PackageManager pm = context.getPackageManager();
+ Bundle b = intent.getExtras();
+ int uid = b.getInt(Intent.EXTRA_UID);
+ String app = intent.getData().getSchemeSpecificPart();
+ PackageInfo pi = pm.getPackageInfo(app, PackageManager.MATCH_ANY_USER);
+ sStatsd.informOnePackage(app, uid, pi.versionCode);
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to inform statsd of an app update", e);
+ }
+ }
+ }
+ };
+
public final static class AnomalyAlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@@ -275,6 +392,23 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
forgetEverything();
}
+ // Setup broadcast receiver for updates
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ mContext.registerReceiverAsUser(mAppUpdateReceiver, UserHandle.ALL, filter, null,
+ null);
+
+ // Setup receiver for user initialize (which happens once for a new user) and
+ // if a user is removed.
+ filter = new IntentFilter(Intent.ACTION_USER_INITIALIZE);
+ filter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiverAsUser(mUserUpdateReceiver, UserHandle.ALL,
+ filter, null, null);
+
+ // Pull the latest state of UID->app name, version mapping when statsd starts.
+ informAllUidsLocked(mContext);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
forgetEverything();
@@ -293,6 +427,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
private void forgetEverything() {
synchronized (sStatsdLock) {
sStatsd = null;
+ mContext.unregisterReceiver(mAppUpdateReceiver);
+ mContext.unregisterReceiver(mUserUpdateReceiver);
cancelAnomalyAlarm();
cancelPollingAlarms();
}
diff --git a/com/android/server/statusbar/StatusBarManagerInternal.java b/com/android/server/statusbar/StatusBarManagerInternal.java
index 08846784..b07fe98d 100644
--- a/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -77,6 +77,7 @@ public interface StatusBarManagerInternal {
void setCurrentUser(int newUserId);
+ boolean isGlobalActionsDisabled();
void setGlobalActionsListener(GlobalActionsListener listener);
void showGlobalActions();
diff --git a/com/android/server/statusbar/StatusBarManagerService.java b/com/android/server/statusbar/StatusBarManagerService.java
index bdfbe481..c78a3406 100644
--- a/com/android/server/statusbar/StatusBarManagerService.java
+++ b/com/android/server/statusbar/StatusBarManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.statusbar;
+import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
+
import android.app.ActivityThread;
import android.app.StatusBarManager;
import android.content.ComponentName;
@@ -363,6 +365,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
}
@Override
+ public boolean isGlobalActionsDisabled() {
+ return (mDisabled2 & DISABLE2_GLOBAL_ACTIONS) != 0;
+ }
+
+ @Override
public void setGlobalActionsListener(GlobalActionsListener listener) {
mGlobalActionListener = listener;
mGlobalActionListener.onStatusBarConnectedChanged(mBar != null);
diff --git a/com/android/server/tv/TvInputHardwareManager.java b/com/android/server/tv/TvInputHardwareManager.java
index 6117da7b..c1607e94 100644
--- a/com/android/server/tv/TvInputHardwareManager.java
+++ b/com/android/server/tv/TvInputHardwareManager.java
@@ -1022,20 +1022,6 @@ class TvInputHardwareManager implements TvInputHal.Callback {
}
}
- @Override
- public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
- synchronized (mImplLock) {
- if (mReleased) {
- throw new IllegalStateException("Device already released.");
- }
- }
- if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
- return false;
- }
- // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
- return false;
- }
-
private boolean startCapture(Surface surface, TvStreamConfig config) {
synchronized (mImplLock) {
if (mReleased) {
diff --git a/com/android/server/usb/UsbAlsaManager.java b/com/android/server/usb/UsbAlsaManager.java
index acc27bee..d359b704 100644
--- a/com/android/server/usb/UsbAlsaManager.java
+++ b/com/android/server/usb/UsbAlsaManager.java
@@ -132,7 +132,9 @@ public final class UsbAlsaManager {
mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
// initial scan
- mCardsParser.scan();
+ if (mCardsParser.scan() != AlsaCardsParser.SCANSTATUS_SUCCESS) {
+ Slog.e(TAG, "Error scanning ASLA cards file.");
+ }
}
public void systemReady() {
@@ -314,7 +316,7 @@ public final class UsbAlsaManager {
return null;
}
- if (!mDevicesParser.scan()) {
+ if (mDevicesParser.scan() != AlsaDevicesParser.SCANSTATUS_SUCCESS) {
Slog.e(TAG, "Error parsing ALSA devices file.");
return null;
}
@@ -530,6 +532,9 @@ public final class UsbAlsaManager {
//
// called by UsbService.dump
public void dump(IndentingPrintWriter pw) {
+ pw.println("Parsers Scan Status:");
+ pw.println(" Cards Parser: " + mCardsParser.getScanStatus());
+ pw.println(" Devices Parser: " + mDevicesParser.getScanStatus());
pw.println("USB Audio Devices:");
for (UsbDevice device : mAudioDevices.keySet()) {
pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device));
diff --git a/com/android/server/usb/UsbHostManager.java b/com/android/server/usb/UsbHostManager.java
index c657a1b4..095fdc63 100644
--- a/com/android/server/usb/UsbHostManager.java
+++ b/com/android/server/usb/UsbHostManager.java
@@ -376,6 +376,8 @@ public class UsbHostManager {
}
}
}
+
+ mUsbAlsaManager.dump(pw);
}
private native void monitorUsbHostBus();
diff --git a/com/android/server/wifi/SelfRecovery.java b/com/android/server/wifi/SelfRecovery.java
index 21a3e0ac..e39e0d5b 100644
--- a/com/android/server/wifi/SelfRecovery.java
+++ b/com/android/server/wifi/SelfRecovery.java
@@ -72,7 +72,7 @@ public class SelfRecovery {
Log.e(TAG, "Invalid trigger reason. Ignoring...");
return;
}
- Log.wtf(TAG, "Triggering recovery for reason: " + REASON_STRINGS[reason]);
+ Log.e(TAG, "Triggering recovery for reason: " + REASON_STRINGS[reason]);
if (reason == REASON_WIFICOND_CRASH || reason == REASON_HAL_CRASH) {
trimPastRestartTimes();
// Ensure there haven't been too many restarts within MAX_RESTARTS_TIME_WINDOW
diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java
index 0b1719db..35dec2e7 100644
--- a/com/android/server/wifi/WifiNative.java
+++ b/com/android/server/wifi/WifiNative.java
@@ -16,6 +16,7 @@
package com.android.server.wifi;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.apf.ApfCapabilities;
import android.net.wifi.IApInterface;
@@ -109,12 +110,12 @@ public class WifiNative {
* @return Pair of <Integer, IClientInterface> to indicate the status and the associated wificond
* client interface binder handler (will be null on failure).
*/
- public Pair<Integer, IClientInterface> setupForClientMode() {
+ public Pair<Integer, IClientInterface> setupForClientMode(@NonNull String ifaceName) {
if (!startHalIfNecessary(true)) {
Log.e(mTAG, "Failed to start HAL for client mode");
return Pair.create(SETUP_FAILURE_HAL, null);
}
- IClientInterface iClientInterface = mWificondControl.setupDriverForClientMode();
+ IClientInterface iClientInterface = mWificondControl.setupDriverForClientMode(ifaceName);
if (iClientInterface == null) {
return Pair.create(SETUP_FAILURE_WIFICOND, null);
}
@@ -130,12 +131,12 @@ public class WifiNative {
* @return Pair of <Integer, IApInterface> to indicate the status and the associated wificond
* AP interface binder handler (will be null on failure).
*/
- public Pair<Integer, IApInterface> setupForSoftApMode() {
+ public Pair<Integer, IApInterface> setupForSoftApMode(@NonNull String ifaceName) {
if (!startHalIfNecessary(false)) {
Log.e(mTAG, "Failed to start HAL for AP mode");
return Pair.create(SETUP_FAILURE_HAL, null);
}
- IApInterface iApInterface = mWificondControl.setupDriverForSoftApMode();
+ IApInterface iApInterface = mWificondControl.setupDriverForSoftApMode(ifaceName);
if (iApInterface == null) {
return Pair.create(SETUP_FAILURE_WIFICOND, null);
}
diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java
index 1f6cada7..0c2cc32f 100644
--- a/com/android/server/wifi/WifiStateMachine.java
+++ b/com/android/server/wifi/WifiStateMachine.java
@@ -4165,7 +4165,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
switch (message.what) {
case CMD_START_SUPPLICANT:
Pair<Integer, IClientInterface> statusAndInterface =
- mWifiNative.setupForClientMode();
+ mWifiNative.setupForClientMode(mInterfaceName);
if (statusAndInterface.first == WifiNative.SETUP_SUCCESS) {
mClientInterface = statusAndInterface.second;
} else {
@@ -6954,7 +6954,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
mMode = config.getTargetMode();
IApInterface apInterface = null;
- Pair<Integer, IApInterface> statusAndInterface = mWifiNative.setupForSoftApMode();
+ Pair<Integer, IApInterface> statusAndInterface =
+ mWifiNative.setupForSoftApMode(mInterfaceName);
if (statusAndInterface.first == WifiNative.SETUP_SUCCESS) {
apInterface = statusAndInterface.second;
} else {
diff --git a/com/android/server/wifi/WifiStateMachinePrime.java b/com/android/server/wifi/WifiStateMachinePrime.java
index 20068849..cd1948f1 100644
--- a/com/android/server/wifi/WifiStateMachinePrime.java
+++ b/com/android/server/wifi/WifiStateMachinePrime.java
@@ -290,7 +290,8 @@ public class WifiStateMachinePrime {
}
try {
- mApInterface = mWificond.createApInterface();
+ mApInterface = mWificond.createApInterface(
+ mWifiInjector.getWifiNative().getInterfaceName());
} catch (RemoteException e1) { }
if (mApInterface == null) {
diff --git a/com/android/server/wifi/WificondControl.java b/com/android/server/wifi/WificondControl.java
index b6104a8c..df4e785b 100644
--- a/com/android/server/wifi/WificondControl.java
+++ b/com/android/server/wifi/WificondControl.java
@@ -16,6 +16,7 @@
package com.android.server.wifi;
+import android.annotation.NonNull;
import android.net.wifi.IApInterface;
import android.net.wifi.IClientInterface;
import android.net.wifi.IPnoScanEvent;
@@ -133,7 +134,7 @@ public class WificondControl {
* @return An IClientInterface as wificond client interface binder handler.
* Returns null on failure.
*/
- public IClientInterface setupDriverForClientMode() {
+ public IClientInterface setupDriverForClientMode(@NonNull String ifaceName) {
Log.d(TAG, "Setting up driver for client mode");
mWificond = mWifiInjector.makeWificond();
if (mWificond == null) {
@@ -143,7 +144,7 @@ public class WificondControl {
IClientInterface clientInterface = null;
try {
- clientInterface = mWificond.createClientInterface();
+ clientInterface = mWificond.createClientInterface(ifaceName);
} catch (RemoteException e1) {
Log.e(TAG, "Failed to get IClientInterface due to remote exception");
return null;
@@ -181,7 +182,7 @@ public class WificondControl {
* @return An IApInterface as wificond Ap interface binder handler.
* Returns null on failure.
*/
- public IApInterface setupDriverForSoftApMode() {
+ public IApInterface setupDriverForSoftApMode(@NonNull String ifaceName) {
Log.d(TAG, "Setting up driver for soft ap mode");
mWificond = mWifiInjector.makeWificond();
if (mWificond == null) {
@@ -191,7 +192,7 @@ public class WificondControl {
IApInterface apInterface = null;
try {
- apInterface = mWificond.createApInterface();
+ apInterface = mWificond.createApInterface(ifaceName);
} catch (RemoteException e1) {
Log.e(TAG, "Failed to get IApInterface due to remote exception");
return null;
diff --git a/com/android/server/wifi/scanner/WificondScannerImpl.java b/com/android/server/wifi/scanner/WificondScannerImpl.java
index fb878e67..10fc8e3e 100644
--- a/com/android/server/wifi/scanner/WificondScannerImpl.java
+++ b/com/android/server/wifi/scanner/WificondScannerImpl.java
@@ -167,12 +167,12 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
+ ",eventHandler=" + eventHandler);
return false;
}
- if (mPendingSingleScanSettings != null
- || (mLastScanSettings != null && mLastScanSettings.singleScanActive)) {
- Log.w(TAG, "A single scan is already running");
- return false;
- }
synchronized (mSettingsLock) {
+ if (mPendingSingleScanSettings != null
+ || (mLastScanSettings != null && mLastScanSettings.singleScanActive)) {
+ Log.w(TAG, "A single scan is already running");
+ return false;
+ }
mPendingSingleScanSettings = settings;
mPendingSingleScanEventHandler = eventHandler;
processPendingScans();
@@ -518,8 +518,10 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
}
private boolean isHwPnoScanRequired() {
- if (mPnoSettings == null) return false;
- return isHwPnoScanRequired(mPnoSettings.isConnected);
+ synchronized (mSettingsLock) {
+ if (mPnoSettings == null) return false;
+ return isHwPnoScanRequired(mPnoSettings.isConnected);
+ }
}
@Override
diff --git a/com/android/server/wifi/util/InformationElementUtil.java b/com/android/server/wifi/util/InformationElementUtil.java
index c8f9ca34..14912b5f 100644
--- a/com/android/server/wifi/util/InformationElementUtil.java
+++ b/com/android/server/wifi/util/InformationElementUtil.java
@@ -247,6 +247,10 @@ public class InformationElementUtil {
"Bad Interworking element length: " + ie.bytes.length);
}
+ if (ie.bytes.length == 3 || ie.bytes.length == 9) {
+ int venueInfo = (int) ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 2);
+ }
+
if (ie.bytes.length == 7 || ie.bytes.length == 9) {
hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6);
}
diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java
index a1eeff84..5d034935 100644
--- a/com/android/server/wm/AppWindowToken.java
+++ b/com/android/server/wm/AppWindowToken.java
@@ -823,7 +823,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
// For freeform windows, we can't freeze the bounds at the moment because this would make
// the resizing unresponsive.
- if (task == null || task.inFreeformWorkspace()) {
+ if (task == null || task.inFreeformWindowingMode()) {
return false;
}
@@ -1310,8 +1310,7 @@ 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.getStack(WINDOWING_MODE_PINNED);
+ final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
if (pinnedStack != null) {
pinnedStack.onAllWindowsDrawn();
}
diff --git a/com/android/server/wm/BlackFrame.java b/com/android/server/wm/BlackFrame.java
index 5c29a0aa..d206554c 100644
--- a/com/android/server/wm/BlackFrame.java
+++ b/com/android/server/wm/BlackFrame.java
@@ -18,7 +18,6 @@ package com.android.server.wm;
import static android.graphics.PixelFormat.OPAQUE;
import static android.view.SurfaceControl.FX_SURFACE_DIM;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -51,14 +50,8 @@ public class BlackFrame {
int w = r-l;
int h = b-t;
- if (DEBUG_SURFACE_TRACE) {
- surface = new WindowSurfaceController.SurfaceTrace(session, "BlackSurface("
- + l + ", " + t + ")",
- w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN);
- } else {
- surface = new SurfaceControl(session, "BlackSurface",
- w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN);
- }
+ surface = new SurfaceControl(session, "BlackSurface",
+ w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN);
surface.setAlpha(1);
surface.setLayerStack(layerStack);
diff --git a/com/android/server/wm/CircularDisplayMask.java b/com/android/server/wm/CircularDisplayMask.java
index ae415413..85f468b5 100644
--- a/com/android/server/wm/CircularDisplayMask.java
+++ b/com/android/server/wm/CircularDisplayMask.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -67,14 +66,9 @@ class CircularDisplayMask {
SurfaceControl ctrl = null;
try {
- if (DEBUG_SURFACE_TRACE) {
- ctrl = new WindowSurfaceController.SurfaceTrace(session, "CircularDisplayMask",
- mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT,
- SurfaceControl.HIDDEN);
- } else {
- ctrl = new SurfaceControl(session, "CircularDisplayMask", mScreenSize.x,
- mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
- }
+ ctrl = new SurfaceControl(session, "CircularDisplayMask", mScreenSize.x,
+ mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+
ctrl.setLayerStack(display.getLayerStack());
ctrl.setLayer(zOrder);
ctrl.setPosition(0, 0);
diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java
index 9e028d38..5bfea989 100644
--- a/com/android/server/wm/ConfigurationContainer.java
+++ b/com/android/server/wm/ConfigurationContainer.java
@@ -21,7 +21,9 @@ 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;
@@ -182,6 +184,11 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
}
+ public boolean inSplitScreenPrimaryWindowingMode() {
+ return mFullConfiguration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+ }
+
/**
* Returns true if this container can be put in either
* {@link WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
@@ -192,6 +199,14 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
}
+ public boolean inPinnedWindowingMode() {
+ return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
+ }
+
+ public boolean inFreeformWindowingMode() {
+ return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ }
+
/** Returns the activity type associated with the the configuration container. */
/*@WindowConfiguration.ActivityType*/
public int getActivityType() {
diff --git a/com/android/server/wm/DimLayer.java b/com/android/server/wm/DimLayer.java
index 708973d5..48181d30 100644
--- a/com/android/server/wm/DimLayer.java
+++ b/com/android/server/wm/DimLayer.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -105,16 +104,10 @@ public class DimLayer {
private void constructSurface(WindowManagerService service) {
service.openSurfaceTransaction();
try {
- if (DEBUG_SURFACE_TRACE) {
- mDimSurface = new WindowSurfaceController.SurfaceTrace(service.mFxSession,
- "DimSurface",
+ mDimSurface = new SurfaceControl(service.mFxSession, mName,
16, 16, PixelFormat.OPAQUE,
SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
- } else {
- mDimSurface = new SurfaceControl(service.mFxSession, mName,
- 16, 16, PixelFormat.OPAQUE,
- SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
- }
+
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
" DIM " + mDimSurface + ": CREATE");
mDimSurface.setLayerStack(mDisplayId);
diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java
index 0e68a8f6..03fdc968 100644
--- a/com/android/server/wm/DisplayContent.java
+++ b/com/android/server/wm/DisplayContent.java
@@ -16,10 +16,9 @@
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.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
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;
@@ -118,7 +117,7 @@ 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.content.pm.PackageManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -297,10 +296,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
/** Window tokens that are in the process of exiting, but still on screen for animations. */
final ArrayList<WindowToken> mExitingTokens = new ArrayList<>();
- /** A special TaskStack with id==HOME_STACK_ID that moves to the bottom whenever any TaskStack
- * (except a future lockscreen TaskStack) moves to the top. */
- private TaskStack mHomeStack = null;
-
/** Detect user tapping outside of current focused task bounds .*/
TaskTapPointerEventListener mTapDetector;
@@ -979,8 +974,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
// In the presence of the PINNED stack or System Alert
- // windows we unforuntately can not seamlessly rotate.
- if (getStackById(PINNED_STACK_ID) != null) {
+ // windows we unfortunately can not seamlessly rotate.
+ if (hasPinnedStack()) {
mayRotateSeamlessly = false;
}
for (int i = 0; i < mService.mSessions.size(); i++) {
@@ -1451,20 +1446,31 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
TaskStack getHomeStack() {
- if (mHomeStack == null && mDisplayId == DEFAULT_DISPLAY) {
- Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this);
- }
- return mHomeStack;
+ return mTaskStackContainers.getHomeStack();
}
- TaskStack getStackById(int stackId) {
- for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
- final TaskStack stack = mTaskStackContainers.get(i);
- if (stack.mStackId == stackId) {
- return stack;
- }
- }
- return null;
+ /**
+ * @return The primary split-screen stack, but only if it is visible, and {@code null} otherwise.
+ */
+ TaskStack getSplitScreenPrimaryStackStack() {
+ TaskStack stack = mTaskStackContainers.getSplitScreenPrimaryStackStack();
+ return (stack != null && stack.isVisible()) ? stack : null;
+ }
+
+ /**
+ * Like {@link #getSplitScreenPrimaryStackStack}, but also returns the stack if it's currently
+ * not visible.
+ */
+ TaskStack getSplitScreenPrimaryStackStackIgnoringVisibility() {
+ return mTaskStackContainers.getSplitScreenPrimaryStackStack();
+ }
+
+ TaskStack getPinnedStack() {
+ return mTaskStackContainers.getPinnedStack();
+ }
+
+ private boolean hasPinnedStack() {
+ return mTaskStackContainers.getPinnedStack() != null;
}
/**
@@ -1480,29 +1486,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* 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;
+ return mTaskStackContainers.getStack(windowingMode, activityType);
}
@VisibleForTesting
- int getStackCount() {
- return mTaskStackContainers.size();
+ TaskStack getTopStack() {
+ return mTaskStackContainers.getTopStack();
}
- @VisibleForTesting
- int getStackPosition(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 i;
- }
- }
- return -1;
+ void onStackWindowingModeChanged(TaskStack stack) {
+ mTaskStackContainers.onStackWindowingModeChanged(stack);
}
@Override
@@ -1523,8 +1516,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* bounds were updated.
*/
void updateStackBoundsAfterConfigChange(@NonNull List<Integer> changedStackList) {
- for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
- final TaskStack stack = mTaskStackContainers.get(i);
+ for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(i);
if (stack.updateBoundsAfterConfigChange()) {
changedStackList.add(stack.mStackId);
}
@@ -1533,7 +1526,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 (getStack(WINDOWING_MODE_PINNED) == null) {
+ if (!hasPinnedStack()) {
mPinnedStackControllerLocked.onDisplayInfoChanged();
}
}
@@ -1632,8 +1625,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mDisplay.getDisplayInfo(mDisplayInfo);
mDisplay.getMetrics(mDisplayMetrics);
- for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
- mTaskStackContainers.get(i).updateDisplayInfo(null);
+ for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+ mTaskStackContainers.getChildAt(i).updateDisplayInfo(null);
}
}
@@ -1754,26 +1747,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
out.set(mContentRect);
}
- TaskStack addStackToDisplay(int stackId, boolean onTop, StackWindowController controller) {
+ TaskStack createStack(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, 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, controller);
- mTaskStackContainers.addStackToDisplay(stack, onTop);
- }
+ final TaskStack stack = new TaskStack(mService, stackId, controller);
+ mTaskStackContainers.addStackToDisplay(stack, onTop);
- if (stackId == DOCKED_STACK_ID) {
+ if (stack.inSplitScreenPrimaryWindowingMode()) {
mDividerControllerLocked.notifyDockedStackExistsChanged(true);
}
return stack;
@@ -1790,7 +1771,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
+ " to its current displayId=" + mDisplayId);
}
- prevDc.mTaskStackContainers.removeStackFromDisplay(stack);
+ prevDc.mTaskStackContainers.removeChild(stack);
mTaskStackContainers.addStackToDisplay(stack, onTop);
}
@@ -1824,8 +1805,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
int taskIdFromPoint(int x, int y) {
- for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
- final TaskStack stack = mTaskStackContainers.get(stackNdx);
+ for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
final int taskId = stack.taskIdFromPoint(x, y);
if (taskId != -1) {
return taskId;
@@ -1841,8 +1822,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
Task findTaskForResizePoint(int x, int y) {
final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
mTmpTaskForResizePointSearchResult.reset();
- for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
- final TaskStack stack = mTaskStackContainers.get(stackNdx);
+ for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
if (!stack.getWindowConfiguration().canResizeTask()) {
return null;
}
@@ -1864,8 +1845,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mTouchExcludeRegion.set(mBaseDisplayRect);
final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
mTmpRect2.setEmpty();
- for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
- final TaskStack stack = mTaskStackContainers.get(stackNdx);
+ for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
stack.setTouchExcludeRegion(
focusedTask, delta, mTouchExcludeRegion, mContentRect, mTmpRect2);
}
@@ -1896,7 +1877,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
}
// TODO(multi-display): Support docked stacks on secondary displays.
- if (mDisplayId == DEFAULT_DISPLAY && getDockedStackLocked() != null) {
+ if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStackStack() != null) {
mDividerControllerLocked.getTouchRegion(mTmpRect);
mTmpRegion.set(mTmpRect);
mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
@@ -1913,8 +1894,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
private void resetAnimationBackgroundAnimator() {
- for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
- mTaskStackContainers.get(stackNdx).resetAnimationBackgroundAnimator();
+ for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ mTaskStackContainers.getChildAt(stackNdx).resetAnimationBackgroundAnimator();
}
}
@@ -1985,8 +1966,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
float dividerAnimationTarget) {
boolean updated = false;
- for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
- final TaskStack stack = mTaskStackContainers.get(i);
+ for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(i);
if (stack == null || !stack.isAdjustedForIme()) {
continue;
}
@@ -2014,8 +1995,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
boolean clearImeAdjustAnimation() {
boolean changed = false;
- for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
- final TaskStack stack = mTaskStackContainers.get(i);
+ for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(i);
if (stack != null && stack.isAdjustedForIme()) {
stack.resetAdjustedForIme(true /* adjustBoundsNow */);
changed = true;
@@ -2025,8 +2006,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
void beginImeAdjustAnimation() {
- for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
- final TaskStack stack = mTaskStackContainers.get(i);
+ for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(i);
if (stack.isVisible() && stack.isAdjustedForIme()) {
stack.beginImeAdjustAnimation();
}
@@ -2037,7 +2018,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
final WindowState imeWin = mService.mInputMethodWindow;
final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
&& !mDividerControllerLocked.isImeHideRequested();
- final boolean dockVisible = isStackVisible(DOCKED_STACK_ID);
+ final boolean dockVisible = isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final TaskStack imeTargetStack = mService.getImeFocusStackLocked();
final int imeDockSide = (dockVisible && imeTargetStack != null) ?
imeTargetStack.getDockSide() : DOCKED_INVALID;
@@ -2055,8 +2036,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
// - If IME is not visible, divider is not moved and is normal width.
if (imeVisible && dockVisible && (imeOnTop || imeOnBottom) && !dockMinimized) {
- for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
- final TaskStack stack = mTaskStackContainers.get(i);
+ for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(i);
final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
if (stack.isVisible() && (imeOnBottom || isDockedOnBottom)
&& stack.inSplitScreenWindowingMode()) {
@@ -2068,8 +2049,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mDividerControllerLocked.setAdjustedForIme(
imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight);
} else {
- for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
- final TaskStack stack = mTaskStackContainers.get(i);
+ for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(i);
stack.resetAdjustedForIme(!dockVisible);
}
mDividerControllerLocked.setAdjustedForIme(
@@ -2100,8 +2081,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
void prepareFreezingTaskBounds() {
- for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
- final TaskStack stack = mTaskStackContainers.get(stackNdx);
+ for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
stack.prepareFreezingTaskBounds();
}
}
@@ -2160,22 +2141,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
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);
+ for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
stack.writeToProto(proto, STACKS);
}
mDividerControllerLocked.writeToProto(proto, DOCKED_STACK_DIVIDER_CONTROLLER);
mPinnedStackControllerLocked.writeToProto(proto, PINNED_STACK_CONTROLLER);
- for (int i = mAboveAppWindowsContainers.size() - 1; i >= 0; --i) {
- final WindowToken windowToken = mAboveAppWindowsContainers.get(i);
+ for (int i = mAboveAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
+ final WindowToken windowToken = mAboveAppWindowsContainers.getChildAt(i);
windowToken.writeToProto(proto, ABOVE_APP_WINDOWS);
}
- for (int i = mBelowAppWindowsContainers.size() - 1; i >= 0; --i) {
- final WindowToken windowToken = mBelowAppWindowsContainers.get(i);
+ for (int i = mBelowAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
+ final WindowToken windowToken = mBelowAppWindowsContainers.getChildAt(i);
windowToken.writeToProto(proto, BELOW_APP_WINDOWS);
}
- for (int i = mImeWindowsContainers.size() - 1; i >= 0; --i) {
- final WindowToken windowToken = mImeWindowsContainers.get(i);
+ for (int i = mImeWindowsContainers.getChildCount() - 1; i >= 0; --i) {
+ final WindowToken windowToken = mImeWindowsContainers.getChildAt(i);
windowToken.writeToProto(proto, IME_WINDOWS);
}
proto.write(DPI, mBaseDisplayDensity);
@@ -2221,8 +2202,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
pw.println();
pw.println(prefix + "Application tokens in top down Z order:");
- for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
- final TaskStack stack = mTaskStackContainers.get(stackNdx);
+ for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
stack.dump(prefix + " ", pw);
}
@@ -2241,6 +2222,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
pw.println();
mDimLayerController.dump(prefix, pw);
pw.println();
+
+ // Dump stack references
+ final TaskStack homeStack = getHomeStack();
+ if (homeStack != null) {
+ pw.println(prefix + "homeStack=" + homeStack.getName());
+ }
+ final TaskStack pinnedStack = getPinnedStack();
+ if (pinnedStack != null) {
+ pw.println(prefix + "pinnedStack=" + pinnedStack.getName());
+ }
+ final TaskStack splitScreenPrimaryStack = getSplitScreenPrimaryStackStack();
+ if (splitScreenPrimaryStack != null) {
+ pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName());
+ }
+
+ pw.println();
mDividerControllerLocked.dump(prefix, pw);
pw.println();
mPinnedStackControllerLocked.dump(prefix, pw);
@@ -2260,26 +2257,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return "Display " + mDisplayId + " name=\"" + mDisplayInfo.name + "\"";
}
- /** Checks if stack with provided id is visible on this display. */
- boolean isStackVisible(int stackId) {
- final TaskStack stack = getStackById(stackId);
- return (stack != null && stack.isVisible());
- }
-
- /**
- * @return The docked stack, but only if it is visible, and {@code null} otherwise.
- */
- TaskStack getDockedStackLocked() {
- final TaskStack stack = getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- return (stack != null && stack.isVisible()) ? stack : null;
- }
-
- /**
- * Like {@link #getDockedStackLocked}, but also returns the docked stack if it's currently not
- * visible.
- */
- TaskStack getDockedStackIgnoringVisibility() {
- return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ /** Returns true if the stack in the windowing mode is visible. */
+ boolean isStackVisible(int windowingMode) {
+ final TaskStack stack = getStack(windowingMode);
+ return stack != null && stack.isVisible();
}
/** Find the visible, touch-deliverable window under the given point */
@@ -3358,14 +3339,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
*/
static class DisplayChildWindowContainer<E extends WindowContainer> extends WindowContainer<E> {
- int size() {
- return mChildren.size();
- }
-
- E get(int index) {
- return mChildren.get(index);
- }
-
@Override
boolean fillsParent() {
return true;
@@ -3383,25 +3356,108 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
*/
private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
+ // Cached reference to some special stacks we tend to get a lot so we don't need to loop
+ // through the list to find them.
+ private TaskStack mHomeStack = null;
+ private TaskStack mPinnedStack = null;
+ private TaskStack mSplitScreenPrimaryStack = null;
+
+ /**
+ * 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) {
+ if (activityType == ACTIVITY_TYPE_HOME) {
+ return mHomeStack;
+ }
+ if (windowingMode == WINDOWING_MODE_PINNED) {
+ return mPinnedStack;
+ } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ return mSplitScreenPrimaryStack;
+ }
+ for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.getChildAt(i);
+ if (stack.isCompatible(windowingMode, activityType)) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ TaskStack getTopStack() {
+ return mTaskStackContainers.getChildCount() > 0
+ ? mTaskStackContainers.getChildAt(mTaskStackContainers.getChildCount() - 1) : null;
+ }
+
+ TaskStack getHomeStack() {
+ if (mHomeStack == null && mDisplayId == DEFAULT_DISPLAY) {
+ Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this);
+ }
+ return mHomeStack;
+ }
+
+ TaskStack getPinnedStack() {
+ return mPinnedStack;
+ }
+
+ TaskStack getSplitScreenPrimaryStackStack() {
+ return mSplitScreenPrimaryStack;
+ }
+
/**
* Adds the stack to this container.
- * @see WindowManagerService#addStackToDisplay(int, int, boolean)
+ * @see DisplayContent#createStack(int, boolean, StackWindowController)
*/
void addStackToDisplay(TaskStack stack, boolean onTop) {
+ addStackReferenceIfNeeded(stack);
+ addChild(stack, onTop);
+ stack.onDisplayChanged(DisplayContent.this);
+ }
+
+ void onStackWindowingModeChanged(TaskStack stack) {
+ removeStackReferenceIfNeeded(stack);
+ addStackReferenceIfNeeded(stack);
+ if (stack == mPinnedStack && getTopStack() != stack) {
+ // Looks like this stack changed windowing mode to pinned. Move it to the top.
+ positionChildAt(POSITION_TOP, stack, false /* includingParents */);
+ }
+ }
+
+ private void addStackReferenceIfNeeded(TaskStack stack) {
if (stack.isActivityTypeHome()) {
if (mHomeStack != null) {
- throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
+ throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
+ + mHomeStack + " already exist on display=" + this + " stack=" + stack);
}
mHomeStack = stack;
}
- addChild(stack, onTop);
- stack.onDisplayChanged(DisplayContent.this);
+ final int windowingMode = stack.getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_PINNED) {
+ if (mPinnedStack != null) {
+ throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack="
+ + mPinnedStack + " already exist on display=" + this
+ + " stack=" + stack);
+ }
+ mPinnedStack = stack;
+ } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ if (mSplitScreenPrimaryStack != null) {
+ throw new IllegalArgumentException("addStackReferenceIfNeeded:"
+ + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack
+ + " already exist on display=" + this + " stack=" + stack);
+ }
+ mSplitScreenPrimaryStack = stack;
+ }
}
- /** Removes the stack from its container and prepare for changing the parent. */
- void removeStackFromDisplay(TaskStack stack) {
- removeChild(stack);
- stack.onRemovedFromDisplay();
+ private void removeStackReferenceIfNeeded(TaskStack stack) {
+ if (stack == mHomeStack) {
+ mHomeStack = null;
+ } else if (stack == mPinnedStack) {
+ mPinnedStack = null;
+ } else if (stack == mSplitScreenPrimaryStack) {
+ mSplitScreenPrimaryStack = null;
+ }
}
private void addChild(TaskStack stack, boolean toTop) {
@@ -3411,6 +3467,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
setLayoutNeeded();
}
+ @Override
+ protected void removeChild(TaskStack stack) {
+ super.removeChild(stack);
+ removeStackReferenceIfNeeded(stack);
+ }
@Override
boolean isOnTop() {
@@ -3453,8 +3514,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
: requestedPosition >= topChildPosition;
int targetPosition = requestedPosition;
- if (toTop && stack.getWindowingMode() != WINDOWING_MODE_PINNED
- && getStack(WINDOWING_MODE_PINNED) != null) {
+ if (toTop && stack.getWindowingMode() != WINDOWING_MODE_PINNED && hasPinnedStack()) {
// The pinned stack is always the top most stack (always-on-top) when it is present.
TaskStack topStack = mChildren.get(topChildPosition);
if (topStack.getWindowingMode() != WINDOWING_MODE_PINNED) {
@@ -3556,7 +3616,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
@Override
int getOrientation() {
- if (isStackVisible(DOCKED_STACK_ID) || isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
+ if (isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
+ || isStackVisible(WINDOWING_MODE_FREEFORM)) {
// Apps and their containers are not allowed to specify an orientation while the
// docked or freeform stack is visible...except for the home stack/task if the
// docked stack is minimized and it actually set something.
@@ -3571,6 +3632,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
final int orientation = super.getOrientation();
+ boolean isCar = mService.mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE);
+ if (isCar) {
+ // In a car, you cannot physically rotate the screen, so it doesn't make sense to
+ // allow anything but the default orientation.
+ if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+ "Forcing UNSPECIFIED orientation in car. Ignoring " + orientation);
+ return SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+
if (orientation != SCREEN_ORIENTATION_UNSET
&& orientation != SCREEN_ORIENTATION_BEHIND) {
if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java
index 6f441b98..52526e2f 100644
--- a/com/android/server/wm/DockedStackDividerController.java
+++ b/com/android/server/wm/DockedStackDividerController.java
@@ -16,9 +16,6 @@
package com.android.server.wm;
-import static android.app.ActivityManager.StackId.DOCKED_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;
@@ -322,7 +319,7 @@ public class DockedStackDividerController implements DimLayerUser {
if (mWindow == null) {
return;
}
- TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility();
+ TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
// If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
final boolean visible = stack != null;
@@ -362,7 +359,7 @@ public class DockedStackDividerController implements DimLayerUser {
}
void positionDockedStackedDivider(Rect frame) {
- TaskStack stack = mDisplayContent.getDockedStackLocked();
+ TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStack();
if (stack == null) {
// Unfortunately we might end up with still having a divider, even though the underlying
// stack was already removed. This is because we are on AM thread and the removal of the
@@ -459,7 +456,7 @@ public class DockedStackDividerController implements DimLayerUser {
long animDuration = 0;
if (animate) {
final TaskStack stack =
- mDisplayContent.getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
final long transitionDuration = isAnimationMaximizing()
? mService.mAppTransition.getLastClipRevealTransitionDuration()
: DEFAULT_APP_TRANSITION_DURATION;
@@ -513,7 +510,8 @@ public class DockedStackDividerController implements DimLayerUser {
void registerDockedStackListener(IDockedStackListener listener) {
mDockedStackListeners.register(listener);
notifyDockedDividerVisibilityChanged(wasVisible());
- notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null);
+ notifyDockedStackExistsChanged(
+ mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null);
notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
isHomeStackResizable());
notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
@@ -532,7 +530,7 @@ public class DockedStackDividerController implements DimLayerUser {
final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
? mDisplayContent.getStack(targetWindowingMode)
: null;
- final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
+ final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStackStack();
boolean visibleAndValid = visible && stack != null && dockedStack != null;
if (visibleAndValid) {
stack.getDimBounds(mTmpRect);
@@ -588,7 +586,7 @@ public class DockedStackDividerController implements DimLayerUser {
private boolean containsAppInDockedStack(ArraySet<AppWindowToken> apps) {
for (int i = apps.size() - 1; i >= 0; i--) {
final AppWindowToken token = apps.valueAt(i);
- if (token.getTask() != null && token.getTask().mStack.mStackId == DOCKED_STACK_ID) {
+ if (token.getTask() != null && token.inSplitScreenPrimaryWindowingMode()) {
return true;
}
}
@@ -600,7 +598,7 @@ public class DockedStackDividerController implements DimLayerUser {
}
private void checkMinimizeChanged(boolean animate) {
- if (mDisplayContent.getDockedStackIgnoringVisibility() == null) {
+ if (mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() == null) {
return;
}
final TaskStack homeStack = mDisplayContent.getHomeStack();
@@ -762,7 +760,7 @@ public class DockedStackDividerController implements DimLayerUser {
}
private boolean setMinimizedDockedStack(boolean minimized) {
- final TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility();
+ final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
}
@@ -813,8 +811,7 @@ public class DockedStackDividerController implements DimLayerUser {
}
private boolean animateForMinimizedDockedStack(long now) {
- final TaskStack stack =
- mDisplayContent.getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
if (!mAnimationStarted) {
mAnimationStarted = true;
mAnimationStartTime = now;
diff --git a/com/android/server/wm/EmulatorDisplayOverlay.java b/com/android/server/wm/EmulatorDisplayOverlay.java
index 3186d3dc..19bd8e9d 100644
--- a/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -57,14 +56,8 @@ class EmulatorDisplayOverlay {
SurfaceControl ctrl = null;
try {
- if (DEBUG_SURFACE_TRACE) {
- ctrl = new WindowSurfaceController.SurfaceTrace(session, "EmulatorDisplayOverlay",
- mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT,
- SurfaceControl.HIDDEN);
- } else {
- ctrl = new SurfaceControl(session, "EmulatorDisplayOverlay", mScreenSize.x,
- mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
- }
+ ctrl = new SurfaceControl(session, "EmulatorDisplayOverlay", mScreenSize.x,
+ mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
ctrl.setLayerStack(display.getLayerStack());
ctrl.setLayer(zOrder);
ctrl.setPosition(0, 0);
diff --git a/com/android/server/wm/InputMonitor.java b/com/android/server/wm/InputMonitor.java
index 5057f632..238cb9f1 100644
--- a/com/android/server/wm/InputMonitor.java
+++ b/com/android/server/wm/InputMonitor.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
@@ -650,7 +649,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
final boolean hasFocus = w == mInputFocus;
final boolean isVisible = w.isVisibleLw();
- if (w.getStackId() == PINNED_STACK_ID) {
+ if (w.inPinnedWindowingMode()) {
if (mAddPipInputConsumerHandle
&& (inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer)) {
// Update the bounds of the Pip input consumer to match the Pinned stack
diff --git a/com/android/server/wm/PinnedStackController.java b/com/android/server/wm/PinnedStackController.java
index ef31598f..365366ad 100644
--- a/com/android/server/wm/PinnedStackController.java
+++ b/com/android/server/wm/PinnedStackController.java
@@ -417,8 +417,7 @@ class PinnedStackController {
false /* useCurrentMinEdgeSize */);
}
final Rect animatingBounds = mTmpAnimatingBoundsRect;
- final TaskStack pinnedStack =
- mDisplayContent.getStack(WINDOWING_MODE_PINNED);
+ final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
if (pinnedStack != null) {
pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
} else {
diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java
index 7832f5de..fd574709 100644
--- a/com/android/server/wm/RootWindowContainer.java
+++ b/com/android/server/wm/RootWindowContainer.java
@@ -411,17 +411,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
}
}
- TaskStack getStackById(int stackId) {
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final DisplayContent dc = mChildren.get(i);
- final TaskStack stack = dc.getStackById(stackId);
- if (stack != null) {
- return stack;
- }
- }
- return null;
- }
-
TaskStack getStack(int windowingMode, int activityType) {
for (int i = mChildren.size() - 1; i >= 0; i--) {
final DisplayContent dc = mChildren.get(i);
diff --git a/com/android/server/wm/ScreenRotationAnimation.java b/com/android/server/wm/ScreenRotationAnimation.java
index d5b6d246..8e99be83 100644
--- a/com/android/server/wm/ScreenRotationAnimation.java
+++ b/com/android/server/wm/ScreenRotationAnimation.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
@@ -24,7 +23,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER;
-import static com.android.server.wm.WindowSurfaceController.SurfaceTrace;
import static com.android.server.wm.proto.ScreenRotationAnimationProto.ANIMATION_RUNNING;
import static com.android.server.wm.proto.ScreenRotationAnimationProto.STARTED;
@@ -276,17 +274,10 @@ class ScreenRotationAnimation {
flags |= SurfaceControl.SECURE;
}
- if (DEBUG_SURFACE_TRACE) {
- mSurfaceControl = new SurfaceTrace(session, "ScreenshotSurface",
- mWidth, mHeight,
- PixelFormat.OPAQUE, flags);
- Slog.w(TAG, "ScreenRotationAnimation ctor: displayOffset="
- + mOriginalDisplayRect.toShortString());
- } else {
- mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface",
- mWidth, mHeight,
- PixelFormat.OPAQUE, flags);
- }
+ mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface",
+ mWidth, mHeight,
+ PixelFormat.OPAQUE, flags);
+
// capture a screenshot into the surface we just created
Surface sur = new Surface();
sur.copyFrom(mSurfaceControl);
diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java
index c0a4cb72..1fda832d 100644
--- a/com/android/server/wm/StackWindowController.java
+++ b/com/android/server/wm/StackWindowController.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
@@ -45,7 +43,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
public class StackWindowController
extends WindowContainerController<TaskStack, StackWindowListener> {
- final int mStackId;
+ private final int mStackId;
private final H mHandler;
@@ -74,7 +72,7 @@ public class StackWindowController
+ " to unknown displayId=" + displayId);
}
- dc.addStackToDisplay(stackId, onTop, this);
+ dc.createStack(stackId, onTop, this);
getRawBounds(outBounds);
}
}
@@ -280,8 +278,9 @@ public class StackWindowController
if (stack.getWindowConfiguration().tasksAreFloating()) {
// Floating tasks should not be resized to the screen's bounds.
- if (mStackId == PINNED_STACK_ID && bounds.width() == mTmpDisplayBounds.width() &&
- bounds.height() == mTmpDisplayBounds.height()) {
+ if (stack.inPinnedWindowingMode()
+ && bounds.width() == mTmpDisplayBounds.width()
+ && bounds.height() == mTmpDisplayBounds.height()) {
// If the bounds we are animating is the same as the fullscreen stack
// dimensions, then apply the same inset calculations that we normally do for
// the fullscreen stack, without intersecting it with the display bounds
diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java
index 7e8d1308..891d637a 100644
--- a/com/android/server/wm/Task.java
+++ b/com/android/server/wm/Task.java
@@ -17,8 +17,6 @@
package com.android.server.wm;
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
@@ -213,7 +211,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
// then we want to preserve our insets so that there will not
// be a jump in the area covered by system decorations. We rely
// on the pinned animation to later unset this value.
- if (stack.mStackId == PINNED_STACK_ID) {
+ if (stack.inPinnedWindowingMode()) {
mPreserveNonFloatingState = true;
} else {
mPreserveNonFloatingState = false;
@@ -421,7 +419,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
return mFillsParent
|| !inSplitScreenSecondaryWindowingMode()
|| displayContent == null
- || displayContent.getDockedStackIgnoringVisibility() != null;
+ || displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null;
}
/** Original bounds of the task if applicable, otherwise fullscreen rect. */
@@ -492,7 +490,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
final boolean dockedResizing = displayContent != null
&& displayContent.mDividerControllerLocked.isResizing();
if (useCurrentBounds()) {
- if (inFreeformWorkspace() && getMaxVisibleBounds(out)) {
+ if (inFreeformWindowingMode() && getMaxVisibleBounds(out)) {
return;
}
@@ -598,14 +596,6 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
return (tokensCount != 0) && mChildren.get(tokensCount - 1).mShowForAllUsers;
}
- boolean inFreeformWorkspace() {
- return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
- }
-
- boolean inPinnedWorkspace() {
- return mStack != null && mStack.mStackId == PINNED_STACK_ID;
- }
-
/**
* When we are in a floating stack (Freeform, Pinned, ...) we calculate
* insets differently. However if we are animating to the fullscreen stack
diff --git a/com/android/server/wm/TaskPositioner.java b/com/android/server/wm/TaskPositioner.java
index c58212cd..87de1514 100644
--- a/com/android/server/wm/TaskPositioner.java
+++ b/com/android/server/wm/TaskPositioner.java
@@ -20,7 +20,6 @@ import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIG
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.RESIZE_MODE_USER;
import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -649,7 +648,7 @@ class TaskPositioner implements DimLayer.DimLayerUser {
* shouldn't be shown.
*/
private int getDimSide(int x) {
- if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
+ if (!mTask.mStack.inFreeformWindowingMode()
|| !mTask.mStack.fillsParent()
|| mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
return CTRL_NONE;
diff --git a/com/android/server/wm/TaskSnapshotController.java b/com/android/server/wm/TaskSnapshotController.java
index bff24f6e..54ef0651 100644
--- a/com/android/server/wm/TaskSnapshotController.java
+++ b/com/android/server/wm/TaskSnapshotController.java
@@ -294,7 +294,9 @@ class TaskSnapshotController {
decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
node.end(c);
final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
-
+ if (hwBitmap == null) {
+ return null;
+ }
return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
topChild.getConfiguration().orientation, mainWindow.mStableInsets,
ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */);
diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java
index 65278837..d170b6f2 100644
--- a/com/android/server/wm/TaskStack.java
+++ b/com/android/server/wm/TaskStack.java
@@ -18,8 +18,6 @@ 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.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;
@@ -296,7 +294,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
if (mFillsParent
|| !inSplitScreenSecondaryWindowingMode()
|| mDisplayContent == null
- || mDisplayContent.getDockedStackLocked() != null) {
+ || mDisplayContent.getSplitScreenPrimaryStackStack() != null) {
return true;
}
return false;
@@ -409,7 +407,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
return false;
}
- if (mStackId == PINNED_STACK_ID) {
+ if (inPinnedWindowingMode()) {
getAnimationOrCurrentBounds(mTmpRect2);
boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
mTmpRect2, mTmpRect3);
@@ -443,21 +441,19 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
mTmpRect2.set(mBounds);
mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
- switch (mStackId) {
- case DOCKED_STACK_ID:
- repositionDockedStackAfterRotation(mTmpRect2);
- snapDockedStackAfterRotation(mTmpRect2);
- final int newDockSide = getDockSide(mTmpRect2);
-
- // Update the dock create mode and clear the dock create bounds, these
- // might change after a rotation and the original values will be invalid.
- mService.setDockedStackCreateStateLocked(
- (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
- ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
- : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
- null);
- mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
- break;
+ if (inSplitScreenPrimaryWindowingMode()) {
+ repositionDockedStackAfterRotation(mTmpRect2);
+ snapDockedStackAfterRotation(mTmpRect2);
+ final int newDockSide = getDockSide(mTmpRect2);
+
+ // Update the dock create mode and clear the dock create bounds, these
+ // might change after a rotation and the original values will be invalid.
+ mService.setDockedStackCreateStateLocked(
+ (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
+ ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
+ : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
+ null);
+ mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
}
mBoundsAfterRotation.set(mTmpRect2);
@@ -677,6 +673,16 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
}
+ @Override
+ public void onConfigurationChanged(Configuration newParentConfig) {
+ final int prevWindowingMode = getWindowingMode();
+ super.onConfigurationChanged(newParentConfig);
+ if (mDisplayContent != null && prevWindowingMode != getWindowingMode()) {
+ mDisplayContent.onStackWindowingModeChanged(this);
+ }
+ }
+
+ @Override
void onDisplayChanged(DisplayContent dc) {
if (mDisplayContent != null) {
throw new IllegalStateException("onDisplayChanged: Already attached");
@@ -687,8 +693,8 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
"animation background stackId=" + mStackId);
Rect bounds = null;
- final TaskStack dockedStack = dc.getDockedStackIgnoringVisibility();
- if (mStackId == DOCKED_STACK_ID
+ final TaskStack dockedStack = dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
+ if (inSplitScreenPrimaryWindowingMode()
|| (dockedStack != null && inSplitScreenSecondaryWindowingMode()
&& !dockedStack.fillsParent())) {
// The existence of a docked stack affects the size of other static stack created since
@@ -703,10 +709,10 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
final boolean dockedOnTopOrLeft = mService.mDockedStackCreateMode
== DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
- getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2,
+ getStackDockedModeBounds(mTmpRect, bounds, mTmpRect2,
mDisplayContent.mDividerControllerLocked.getContentWidth(),
dockedOnTopOrLeft);
- } else if (mStackId == PINNED_STACK_ID) {
+ } else if (inPinnedWindowingMode()) {
// Update the bounds based on any changes to the display info
getAnimationOrCurrentBounds(mTmpRect2);
boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
@@ -766,7 +772,8 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
return;
}
- final TaskStack dockedStack = mDisplayContent.getDockedStackIgnoringVisibility();
+ final TaskStack dockedStack =
+ mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
if (dockedStack == null) {
// Not sure why you are calling this method when there is no docked stack...
throw new IllegalStateException(
@@ -791,7 +798,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
mDisplayContent.getLogicalDisplayRect(mTmpRect);
dockedStack.getRawBounds(mTmpRect2);
final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT;
- getStackDockedModeBounds(mTmpRect, outStackBounds, mStackId, mTmpRect2,
+ getStackDockedModeBounds(mTmpRect, outStackBounds, mTmpRect2,
mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft);
}
@@ -800,16 +807,15 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
* Outputs the bounds a stack should be given the presence of a docked stack on the display.
* @param displayRect The bounds of the display the docked stack is on.
* @param outBounds Output bounds that should be used for the stack.
- * @param stackId Id of stack we are calculating the bounds for.
* @param dockedBounds Bounds of the docked stack.
* @param dockDividerWidth We need to know the width of the divider make to the output bounds
* close to the side of the dock.
* @param dockOnTopOrLeft If the docked stack is on the top or left side of the screen.
*/
private void getStackDockedModeBounds(
- Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int dockDividerWidth,
+ Rect displayRect, Rect outBounds, Rect dockedBounds, int dockDividerWidth,
boolean dockOnTopOrLeft) {
- final boolean dockedStack = stackId == DOCKED_STACK_ID;
+ final boolean dockedStack = inSplitScreenPrimaryWindowingMode();
final boolean splitHorizontally = displayRect.width() > displayRect.height();
outBounds.set(displayRect);
@@ -866,7 +872,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
void resetDockedStackToMiddle() {
- if (mStackId != DOCKED_STACK_ID) {
+ if (inSplitScreenPrimaryWindowingMode()) {
throw new IllegalStateException("Not a docked stack=" + this);
}
@@ -894,17 +900,12 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
@Override
- void removeImmediately() {
- super.removeImmediately();
-
- onRemovedFromDisplay();
- }
+ void onParentSet() {
+ if (getParent() != null || mDisplayContent == null) {
+ return;
+ }
- /**
- * Removes the stack it from its current parent, so it can be either destroyed completely or
- * re-parented.
- */
- void onRemovedFromDisplay() {
+ // Looks like the stack was removed from the display. Go ahead and clean things up.
mDisplayContent.mDimLayerController.removeDimLayerUser(this);
EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
@@ -913,7 +914,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
mAnimationBackgroundSurface = null;
}
- if (mStackId == DOCKED_STACK_ID) {
+ if (inSplitScreenPrimaryWindowingMode()) {
mDisplayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(false);
}
@@ -1035,8 +1036,8 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
boolean shouldIgnoreInput() {
- return isAdjustedForMinimizedDockedStack() || mStackId == DOCKED_STACK_ID &&
- isMinimizedDockAndHomeStackResizable();
+ return isAdjustedForMinimizedDockedStack() ||
+ (inSplitScreenPrimaryWindowingMode() && isMinimizedDockAndHomeStackResizable());
}
/**
@@ -1471,7 +1472,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
postExclude.set(mTmpRect);
}
- final boolean isFreeformed = task.inFreeformWorkspace();
+ final boolean isFreeformed = task.inFreeformWindowingMode();
if (task != focusedTask || isFreeformed) {
if (isFreeformed) {
// If the task is freeformed, enlarge the area to account for outside
@@ -1529,7 +1530,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
}
- if (mStackId == PINNED_STACK_ID) {
+ if (inPinnedWindowingMode()) {
try {
mService.mActivityManager.notifyPinnedStackAnimationStarted();
} catch (RemoteException e) {
@@ -1561,7 +1562,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
mService.requestTraversal();
}
- if (mStackId == PINNED_STACK_ID) {
+ if (inPinnedWindowingMode()) {
// Update to the final bounds if requested. This is done here instead of in the bounds
// animator to allow us to coordinate this after we notify the PiP mode changed
@@ -1595,7 +1596,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
* bounds and we have a deferred PiP mode changed callback set with the animation.
*/
public boolean deferScheduleMultiWindowModeChanged() {
- if (mStackId == PINNED_STACK_ID) {
+ if (inPinnedWindowingMode()) {
return (mBoundsAnimatingRequested || mBoundsAnimating);
}
return false;
diff --git a/com/android/server/wm/WallpaperController.java b/com/android/server/wm/WallpaperController.java
index 7213c951..629cc868 100644
--- a/com/android/server/wm/WallpaperController.java
+++ b/com/android/server/wm/WallpaperController.java
@@ -18,7 +18,7 @@ package com.android.server.wm;
import com.android.internal.util.ToBooleanFunction;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
@@ -447,7 +447,7 @@ class WallpaperController {
private void findWallpaperTarget(DisplayContent dc) {
mFindResults.reset();
- if (dc.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
+ if (dc.isStackVisible(WINDOWING_MODE_FREEFORM)) {
// In freeform mode we set the wallpaper as its own target, so we don't need an
// additional window to make it visible.
mFindResults.setUseTopWallpaperAsTarget(true);
diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java
index 40923c82..1b0825e5 100644
--- a/com/android/server/wm/WindowContainer.java
+++ b/com/android/server/wm/WindowContainer.java
@@ -73,12 +73,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
@Override
- final protected int getChildCount() {
+ protected int getChildCount() {
return mChildren.size();
}
@Override
- final protected E getChildAt(int index) {
+ protected E getChildAt(int index) {
return mChildren.get(index);
}
diff --git a/com/android/server/wm/WindowManagerDebugConfig.java b/com/android/server/wm/WindowManagerDebugConfig.java
index 6d5673e2..9d9805ab 100644
--- a/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/com/android/server/wm/WindowManagerDebugConfig.java
@@ -60,7 +60,6 @@ public class WindowManagerDebugConfig {
static final boolean DEBUG_SCREENSHOT = false;
static final boolean DEBUG_BOOT = false;
static final boolean DEBUG_LAYOUT_REPEATS = false;
- static final boolean DEBUG_SURFACE_TRACE = false;
static final boolean DEBUG_WINDOW_TRACE = false;
static final boolean DEBUG_TASK_MOVEMENT = false;
static final boolean DEBUG_TASK_POSITIONING = false;
diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java
index 1fb21887..b133bd45 100644
--- a/com/android/server/wm/WindowManagerService.java
+++ b/com/android/server/wm/WindowManagerService.java
@@ -2384,7 +2384,7 @@ public class WindowManagerService extends IWindowManager.Stub
final Rect insets = new Rect();
final Rect stableInsets = new Rect();
Rect surfaceInsets = null;
- final boolean freeform = win != null && win.inFreeformWorkspace();
+ final boolean freeform = win != null && win.inFreeformWindowingMode();
if (win != null) {
// Containing frame will usually cover the whole screen, including dialog windows.
// For freeform workspace windows it will not cover the whole screen and it also
@@ -2794,7 +2794,7 @@ public class WindowManagerService extends IWindowManager.Stub
for (final WindowState win : mWindowMap.values()) {
final Task task = win.getTask();
if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1
- && task.inFreeformWorkspace()) {
+ && task.inFreeformWindowingMode()) {
final AppWindowToken appToken = win.mAppToken;
if (appToken != null && appToken.mAppAnimator != null) {
appToken.mAppAnimator.startProlongAnimation(scaleUp ?
@@ -3391,7 +3391,8 @@ public class WindowManagerService extends IWindowManager.Stub
// Notify whether the docked stack exists for the current user
final DisplayContent displayContent = getDefaultDisplayContentLocked();
- final TaskStack stack = displayContent.getDockedStackIgnoringVisibility();
+ final TaskStack stack =
+ displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
stack != null && stack.hasTaskForUser(newUserId));
@@ -6898,11 +6899,6 @@ public class WindowManagerService extends IWindowManager.Stub
dumpSessionsLocked(pw, true);
}
return;
- } else if ("surfaces".equals(cmd)) {
- synchronized(mWindowMap) {
- WindowSurfaceController.SurfaceTrace.dumpAllSurfaces(pw, null);
- }
- return;
} else if ("displays".equals(cmd) || "d".equals(cmd)) {
synchronized(mWindowMap) {
mRoot.dumpDisplayContents(pw);
@@ -6967,10 +6963,6 @@ public class WindowManagerService extends IWindowManager.Stub
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- WindowSurfaceController.SurfaceTrace.dumpAllSurfaces(pw, dumpAll ?
- "-------------------------------------------------------------------------------"
- : null);
- pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
@@ -7132,7 +7124,7 @@ public class WindowManagerService extends IWindowManager.Stub
public int getDockedStackSide() {
synchronized (mWindowMap) {
final TaskStack dockedStack = getDefaultDisplayContentLocked()
- .getDockedStackIgnoringVisibility();
+ .getSplitScreenPrimaryStackStackIgnoringVisibility();
return dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide();
}
}
@@ -7604,10 +7596,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public boolean isStackVisible(int stackId) {
+ public boolean isStackVisible(int windowingMode) {
synchronized (mWindowMap) {
final DisplayContent dc = getDefaultDisplayContentLocked();
- return dc.isStackVisible(stackId);
+ return dc.isStackVisible(windowingMode);
}
}
diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java
index 4ff0f391..e1715284 100644
--- a/com/android/server/wm/WindowState.java
+++ b/com/android/server/wm/WindowState.java
@@ -16,10 +16,7 @@
package com.android.server.wm;
-import static android.app.ActivityManager.StackId;
-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;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
@@ -83,7 +80,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -815,13 +811,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final WindowState imeWin = mService.mInputMethodWindow;
// IME is up and obscuring this window. Adjust the window position so it is visible.
if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this) {
- final int stackId = getStackId();
- if (stackId == FREEFORM_WORKSPACE_STACK_ID
+ if (inFreeformWindowingMode()
&& mContainingFrame.bottom > contentFrame.bottom) {
// In freeform we want to move the top up directly.
// TODO: Investigate why this is contentFrame not parentFrame.
mContainingFrame.top -= mContainingFrame.bottom - contentFrame.bottom;
- } else if (stackId != PINNED_STACK_ID
+ } else if (!inPinnedWindowingMode()
&& mContainingFrame.bottom > parentFrame.bottom) {
// But in docked we want to behave like fullscreen and behave as if the task
// were given smaller bounds for the purposes of layout. Skip adjustments for
@@ -898,7 +893,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// For pinned workspace the frame isn't limited in any particular
// way since SystemUI controls the bounds. For freeform however
// we want to keep things inside the content frame.
- final Rect limitFrame = task.inPinnedWorkspace() ? mFrame : mContentFrame;
+ final Rect limitFrame = task.inPinnedWindowingMode() ? mFrame : mContentFrame;
// Keep the frame out of the blocked system area, limit it in size to the content area
// and make sure that there is always a minimum visible so that the user can drag it
// into a usable area..
@@ -1210,7 +1205,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// application when it has finished drawing.
if (getOrientationChanging() || dragResizingChanged
|| isResizedWhileNotDragResizing()) {
- if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
+ if (DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
Slog.v(TAG_WM, "Orientation or resize start waiting for draw"
+ ", mDrawState=DRAW_PENDING in " + this
+ ", surfaceController " + winAnimator.mSurfaceController);
@@ -1662,9 +1657,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
//
// Anyway we don't need to synchronize position and content updates for these
// windows since they aren't at the base layer and could be moved around anyway.
- if (!computeDragResizing() && mAttrs.type == TYPE_BASE_APPLICATION &&
- !mWinAnimator.isForceScaled() && !isGoneForLayoutLw() &&
- !getTask().inPinnedWorkspace()) {
+ if (!computeDragResizing() && mAttrs.type == TYPE_BASE_APPLICATION
+ && !mWinAnimator.isForceScaled() && !isGoneForLayoutLw()
+ && !getTask().inPinnedWindowingMode()) {
setResizedWhileNotDragResizing(true);
}
}
@@ -2196,12 +2191,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
- // TODO: Strange usage of word workspace here and above.
- boolean inPinnedWorkspace() {
- final Task task = getTask();
- return task != null && task.inPinnedWorkspace();
- }
-
void applyAdjustForImeIfNeeded() {
final Task task = getTask();
if (task != null && task.mStack != null && task.mStack.isAdjustedForIme()) {
@@ -2235,7 +2224,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
} else {
getVisibleBounds(mTmpRect);
}
- if (inFreeformWorkspace()) {
+ if (inFreeformWindowingMode()) {
// For freeform windows we the touch region to include the whole surface for the
// shadows.
final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
@@ -2371,7 +2360,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// just in case they have the divider at an unstable position. Better
// also reset drag resizing state, because the owner can't do it
// anymore.
- final TaskStack stack = dc.getDockedStackIgnoringVisibility();
+ final TaskStack stack =
+ dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
if (stack != null) {
stack.resetDockedStackToMiddle();
}
@@ -2938,8 +2928,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mTmpRect;
}
- @Override
- public int getStackId() {
+ private int getStackId() {
final TaskStack stack = getStack();
if (stack == null) {
return INVALID_STACK_ID;
@@ -2983,11 +2972,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
- boolean inFreeformWorkspace() {
- final Task task = getTask();
- return task != null && task.inFreeformWorkspace();
- }
-
@Override
public boolean isInMultiWindowMode() {
final Task task = getTask();
@@ -3105,7 +3089,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// background.
return (getDisplayContent().mDividerControllerLocked.isResizing()
|| mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) &&
- !task.inFreeformWorkspace() && !isGoneForLayoutLw();
+ !task.inFreeformWindowingMode() && !isGoneForLayoutLw();
}
@@ -3695,7 +3679,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Force the show in the next prepareSurfaceLocked() call.
mWinAnimator.mLastAlpha = -1;
- if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) Slog.v(TAG,
+ if (DEBUG_ANIM) Slog.v(TAG,
"performShowLocked: mDrawState=HAS_DRAWN in " + this);
mWinAnimator.mDrawState = HAS_DRAWN;
mService.scheduleAnimationLocked();
@@ -3756,7 +3740,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
windowInfo.accessibilityIdOfAnchor = mAttrs.accessibilityIdOfAnchor;
windowInfo.focused = isFocused();
Task task = getTask();
- windowInfo.inPictureInPicture = (task != null) && task.inPinnedWorkspace();
+ windowInfo.inPictureInPicture = (task != null) && task.inPinnedWindowingMode();
if (mIsChildWindow) {
windowInfo.parentToken = getParentWindow().mClient.asBinder();
@@ -4219,7 +4203,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// If a freeform window is animating from a position where it would be cutoff, it would be
// cutoff during the animation. We don't want that, so for the duration of the animation
// we ignore the decor cropping and depend on layering to position windows correctly.
- final boolean cropToDecor = !(inFreeformWorkspace() && isAnimatingLw());
+ final boolean cropToDecor = !(inFreeformWindowingMode() && isAnimatingLw());
if (cropToDecor) {
// Intersect with the decor rect, offsetted by window position.
systemDecorRect.intersect(decorRect.left - left, decorRect.top - top,
@@ -4303,7 +4287,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// scale for the animation using the source hint rect
// (see WindowStateAnimator#setSurfaceBoundariesLocked()).
if (isDragResizeChanged() || isResizedWhileNotDragResizing()
- || (surfaceInsetsChanging() && !inPinnedWorkspace())) {
+ || (surfaceInsetsChanging() && !inPinnedWindowingMode())) {
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
setDragResizing();
diff --git a/com/android/server/wm/WindowStateAnimator.java b/com/android/server/wm/WindowStateAnimator.java
index 1b7e5278..52669031 100644
--- a/com/android/server/wm/WindowStateAnimator.java
+++ b/com/android/server/wm/WindowStateAnimator.java
@@ -31,7 +31,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEAT
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_CROP;
@@ -509,7 +508,7 @@ class WindowStateAnimator {
boolean layoutNeeded = false;
if (mDrawState == DRAW_PENDING) {
- if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
+ if (DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + mWin + " in "
+ mSurfaceController);
if (DEBUG_STARTING_WINDOW && startingWindow) {
@@ -532,7 +531,7 @@ class WindowStateAnimator {
if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
return false;
}
- if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) {
+ if (DEBUG_ANIM) {
Slog.i(TAG, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW " + mSurfaceController);
}
mDrawState = READY_TO_SHOW;
@@ -1033,7 +1032,7 @@ class WindowStateAnimator {
//Slog.i(TAG_WM, "Not applying alpha transform");
}
- if ((DEBUG_SURFACE_TRACE || WindowManagerService.localLOGV)
+ if ((DEBUG_ANIM || WindowManagerService.localLOGV)
&& (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
+ " self=" + (selfTransformation ? mTransformation.getAlpha() : "null")
@@ -1112,7 +1111,7 @@ class WindowStateAnimator {
*/
private boolean useFinalClipRect() {
return (isAnimationSet() && resolveStackClip() == STACK_CLIP_AFTER_ANIM)
- || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWorkspace();
+ || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWindowingMode();
}
/**
@@ -1177,7 +1176,7 @@ class WindowStateAnimator {
return false;
}
- if (w.inPinnedWorkspace()) {
+ if (w.inPinnedWindowingMode()) {
return false;
}
diff --git a/com/android/server/wm/WindowSurfaceController.java b/com/android/server/wm/WindowSurfaceController.java
index 2e1e3f76..d56df55d 100644
--- a/com/android/server/wm/WindowSurfaceController.java
+++ b/com/android/server/wm/WindowSurfaceController.java
@@ -22,7 +22,6 @@ import static android.view.Surface.SCALING_MODE_SCALE_TO_WINDOW;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -565,262 +564,4 @@ class WindowSurfaceController {
public String toString() {
return mSurfaceControl.toString();
}
-
- static class SurfaceTrace extends SurfaceControl {
- private final static String SURFACE_TAG = TAG_WITH_CLASS_NAME ? "SurfaceTrace" : TAG_WM;
- private final static boolean LOG_SURFACE_TRACE = DEBUG_SURFACE_TRACE;
- final static ArrayList<SurfaceTrace> sSurfaces = new ArrayList<SurfaceTrace>();
-
- private float mSurfaceTraceAlpha = 0;
- private int mLayer;
- private final PointF mPosition = new PointF();
- private final Point mSize = new Point();
- private final Rect mWindowCrop = new Rect();
- private final Rect mFinalCrop = new Rect();
- private boolean mShown = false;
- private int mLayerStack;
- private boolean mIsOpaque;
- private float mDsdx, mDtdx, mDsdy, mDtdy;
- private final String mName;
-
- public SurfaceTrace(SurfaceSession s, String name, int w, int h, int format, int flags,
- int windowType, int ownerUid)
- throws OutOfResourcesException {
- super(s, name, w, h, format, flags, windowType, ownerUid);
- mName = name != null ? name : "Not named";
- mSize.set(w, h);
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by "
- + Debug.getCallers(3));
- synchronized (sSurfaces) {
- sSurfaces.add(0, this);
- }
- }
-
- public SurfaceTrace(SurfaceSession s,
- String name, int w, int h, int format, int flags) {
- super(s, name, w, h, format, flags);
- mName = name != null ? name : "Not named";
- mSize.set(w, h);
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by "
- + Debug.getCallers(3));
- synchronized (sSurfaces) {
- sSurfaces.add(0, this);
- }
- }
-
- @Override
- public void setAlpha(float alpha) {
- if (mSurfaceTraceAlpha != alpha) {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setAlpha(" + alpha + "): OLD:" + this +
- ". Called by " + Debug.getCallers(3));
- mSurfaceTraceAlpha = alpha;
- }
- super.setAlpha(alpha);
- }
-
- @Override
- public void setLayer(int zorder) {
- if (zorder != mLayer) {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setLayer(" + zorder + "): OLD:" + this
- + ". Called by " + Debug.getCallers(3));
- mLayer = zorder;
- }
- super.setLayer(zorder);
-
- synchronized (sSurfaces) {
- sSurfaces.remove(this);
- int i;
- for (i = sSurfaces.size() - 1; i >= 0; i--) {
- SurfaceTrace s = sSurfaces.get(i);
- if (s.mLayer < zorder) {
- break;
- }
- }
- sSurfaces.add(i + 1, this);
- }
- }
-
- @Override
- public void setPosition(float x, float y) {
- if (x != mPosition.x || y != mPosition.y) {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setPosition(" + x + "," + y + "): OLD:"
- + this + ". Called by " + Debug.getCallers(3));
- mPosition.set(x, y);
- }
- super.setPosition(x, y);
- }
-
- @Override
- public void setGeometryAppliesWithResize() {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setGeometryAppliesWithResize(): OLD: "
- + this + ". Called by" + Debug.getCallers(3));
- super.setGeometryAppliesWithResize();
- }
-
- @Override
- public void setSize(int w, int h) {
- if (w != mSize.x || h != mSize.y) {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setSize(" + w + "," + h + "): OLD:"
- + this + ". Called by " + Debug.getCallers(3));
- mSize.set(w, h);
- }
- super.setSize(w, h);
- }
-
- @Override
- public void setWindowCrop(Rect crop) {
- if (crop != null) {
- if (!crop.equals(mWindowCrop)) {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setWindowCrop("
- + crop.toShortString() + "): OLD:" + this + ". Called by "
- + Debug.getCallers(3));
- mWindowCrop.set(crop);
- }
- }
- super.setWindowCrop(crop);
- }
-
- @Override
- public void setFinalCrop(Rect crop) {
- if (crop != null) {
- if (!crop.equals(mFinalCrop)) {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setFinalCrop("
- + crop.toShortString() + "): OLD:" + this + ". Called by "
- + Debug.getCallers(3));
- mFinalCrop.set(crop);
- }
- }
- super.setFinalCrop(crop);
- }
-
- @Override
- public void setLayerStack(int layerStack) {
- if (layerStack != mLayerStack) {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setLayerStack(" + layerStack + "): OLD:"
- + this + ". Called by " + Debug.getCallers(3));
- mLayerStack = layerStack;
- }
- super.setLayerStack(layerStack);
- }
-
- @Override
- public void setOpaque(boolean isOpaque) {
- if (isOpaque != mIsOpaque) {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setOpaque(" + isOpaque + "): OLD:"
- + this + ". Called by " + Debug.getCallers(3));
- mIsOpaque = isOpaque;
- }
- super.setOpaque(isOpaque);
- }
-
- @Override
- public void setSecure(boolean isSecure) {
- super.setSecure(isSecure);
- }
-
- @Override
- public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
- if (dsdx != mDsdx || dtdx != mDtdx || dsdy != mDsdy || dtdy != mDtdy) {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setMatrix(" + dsdx + "," + dtdx + ","
- + dsdy + "," + dtdy + "): OLD:" + this + ". Called by "
- + Debug.getCallers(3));
- mDsdx = dsdx;
- mDtdx = dtdx;
- mDsdy = dsdy;
- mDtdy = dtdy;
- }
- super.setMatrix(dsdx, dtdx, dsdy, dtdy);
- }
-
- @Override
- public void hide() {
- if (mShown) {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "hide: OLD:" + this + ". Called by "
- + Debug.getCallers(3));
- mShown = false;
- }
- super.hide();
- }
-
- @Override
- public void show() {
- if (!mShown) {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "show: OLD:" + this + ". Called by "
- + Debug.getCallers(3));
- mShown = true;
- }
- super.show();
- }
-
- @Override
- public void destroy() {
- super.destroy();
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "destroy: " + this + ". Called by "
- + Debug.getCallers(3));
- synchronized (sSurfaces) {
- sSurfaces.remove(this);
- }
- }
-
- @Override
- public void release() {
- super.release();
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "release: " + this + ". Called by "
- + Debug.getCallers(3));
- synchronized (sSurfaces) {
- sSurfaces.remove(this);
- }
- }
-
- @Override
- public void setTransparentRegionHint(Region region) {
- if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setTransparentRegionHint(" + region
- + "): OLD: " + this + " . Called by " + Debug.getCallers(3));
- super.setTransparentRegionHint(region);
- }
-
- static void dumpAllSurfaces(PrintWriter pw, String header) {
- synchronized (sSurfaces) {
- final int N = sSurfaces.size();
- if (N <= 0) {
- return;
- }
- if (header != null) {
- pw.println(header);
- }
- pw.println("WINDOW MANAGER SURFACES (dumpsys window surfaces)");
- for (int i = 0; i < N; i++) {
- SurfaceTrace s = sSurfaces.get(i);
- pw.print(" Surface #"); pw.print(i); pw.print(": #");
- pw.print(Integer.toHexString(System.identityHashCode(s)));
- pw.print(" "); pw.println(s.mName);
- pw.print(" mLayerStack="); pw.print(s.mLayerStack);
- pw.print(" mLayer="); pw.println(s.mLayer);
- pw.print(" mShown="); pw.print(s.mShown); pw.print(" mAlpha=");
- pw.print(s.mSurfaceTraceAlpha); pw.print(" mIsOpaque=");
- pw.println(s.mIsOpaque);
- pw.print(" mPosition="); pw.print(s.mPosition.x); pw.print(",");
- pw.print(s.mPosition.y);
- pw.print(" mSize="); pw.print(s.mSize.x); pw.print("x");
- pw.println(s.mSize.y);
- pw.print(" mCrop="); s.mWindowCrop.printShortString(pw); pw.println();
- pw.print(" mFinalCrop="); s.mFinalCrop.printShortString(pw); pw.println();
- pw.print(" Transform: ("); pw.print(s.mDsdx); pw.print(", ");
- pw.print(s.mDtdx); pw.print(", "); pw.print(s.mDsdy);
- pw.print(", "); pw.print(s.mDtdy); pw.println(")");
- }
- }
- }
-
- @Override
- public String toString() {
- return "Surface " + Integer.toHexString(System.identityHashCode(this)) + " "
- + mName + " (" + mLayerStack + "): shown=" + mShown + " layer=" + mLayer
- + " alpha=" + mSurfaceTraceAlpha + " " + mPosition.x + "," + mPosition.y
- + " " + mSize.x + "x" + mSize.y
- + " crop=" + mWindowCrop.toShortString()
- + " opaque=" + mIsOpaque
- + " (" + mDsdx + "," + mDtdx + "," + mDsdy + "," + mDtdy + ")";
- }
- }
}
diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java
index af1fa2fe..fa33fe8f 100644
--- a/com/android/server/wm/WindowSurfacePlacer.java
+++ b/com/android/server/wm/WindowSurfacePlacer.java
@@ -449,6 +449,9 @@ class WindowSurfacePlacer {
// animating?
wtoken.setVisibility(animLp, false, transit, false, voiceInteraction);
wtoken.updateReportedVisibilityLocked();
+ // setAllAppWinAnimators so the windows get onExitAnimationDone once the animation is
+ // done.
+ wtoken.setAllAppWinAnimators();
// Force the allDrawn flag, because we want to start
// this guy's animations regardless of whether it's
// gotten drawn.
diff --git a/com/android/settingslib/CustomEditTextPreference.java b/com/android/settingslib/CustomEditTextPreference.java
index 253ca11b..90124f1f 100644
--- a/com/android/settingslib/CustomEditTextPreference.java
+++ b/com/android/settingslib/CustomEditTextPreference.java
@@ -20,6 +20,7 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
+import android.support.annotation.CallSuper;
import android.support.v14.preference.EditTextPreferenceDialogFragment;
import android.support.v7.preference.EditTextPreference;
import android.util.AttributeSet;
@@ -30,7 +31,8 @@ public class CustomEditTextPreference extends EditTextPreference {
private CustomPreferenceDialogFragment mFragment;
- public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@@ -69,7 +71,12 @@ public class CustomEditTextPreference extends EditTextPreference {
protected void onClick(DialogInterface dialog, int which) {
}
+ @CallSuper
protected void onBindDialogView(View view) {
+ final EditText editText = view.findViewById(android.R.id.edit);
+ if (editText != null) {
+ editText.requestFocus();
+ }
}
private void setFragment(CustomPreferenceDialogFragment fragment) {
diff --git a/com/android/settingslib/development/AbstractLogdSizePreferenceController.java b/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
index 7998b2ef..f79be7ea 100644
--- a/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
+++ b/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
@@ -33,21 +33,27 @@ public abstract class AbstractLogdSizePreferenceController extends
+ "AbstractLogdSizePreferenceController.LOGD_SIZE_UPDATED";
public static final String EXTRA_CURRENT_LOGD_VALUE = "CURRENT_LOGD_VALUE";
+ @VisibleForTesting
+ static final String LOW_RAM_CONFIG_PROPERTY_KEY = "ro.config.low_ram";
private static final String SELECT_LOGD_SIZE_KEY = "select_logd_size";
@VisibleForTesting
static final String SELECT_LOGD_SIZE_PROPERTY = "persist.logd.size";
static final String SELECT_LOGD_TAG_PROPERTY = "persist.log.tag";
// Tricky, isLoggable only checks for first character, assumes silence
static final String SELECT_LOGD_TAG_SILENCE = "Settings";
- private static final String SELECT_LOGD_SNET_TAG_PROPERTY = "persist.log.tag.snet_event_log";
+ @VisibleForTesting
+ static final String SELECT_LOGD_SNET_TAG_PROPERTY = "persist.log.tag.snet_event_log";
private static final String SELECT_LOGD_RUNTIME_SNET_TAG_PROPERTY = "log.tag.snet_event_log";
private static final String SELECT_LOGD_DEFAULT_SIZE_PROPERTY = "ro.logd.size";
@VisibleForTesting
static final String SELECT_LOGD_DEFAULT_SIZE_VALUE = "262144";
private static final String SELECT_LOGD_SVELTE_DEFAULT_SIZE_VALUE = "65536";
// 32768 is merely a menu marker, 64K is our lowest log buffer size we replace it with.
- private static final String SELECT_LOGD_MINIMUM_SIZE_VALUE = "65536";
+ @VisibleForTesting
+ static final String SELECT_LOGD_MINIMUM_SIZE_VALUE = "65536";
static final String SELECT_LOGD_OFF_SIZE_MARKER_VALUE = "32768";
+ @VisibleForTesting
+ static final String DEFAULT_SNET_TAG = "I";
private ListPreference mLogdSize;
@@ -154,7 +160,7 @@ public abstract class AbstractLogdSizePreferenceController extends
if ((snetValue == null) || (snetValue.length() == 0)) {
snetValue = SystemProperties.get(SELECT_LOGD_RUNTIME_SNET_TAG_PROPERTY);
if ((snetValue == null) || (snetValue.length() == 0)) {
- SystemProperties.set(SELECT_LOGD_SNET_TAG_PROPERTY, "I");
+ SystemProperties.set(SELECT_LOGD_SNET_TAG_PROPERTY, DEFAULT_SNET_TAG);
}
}
// Silence all log sources, security logs notwithstanding
diff --git a/com/android/settingslib/development/AbstractLogpersistPreferenceController.java b/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
index 67553adc..77b2d86c 100644
--- a/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
+++ b/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
@@ -68,7 +68,7 @@ public abstract class AbstractLogpersistPreferenceController extends
public AbstractLogpersistPreferenceController(Context context, Lifecycle lifecycle) {
super(context);
- if (isAvailable()) {
+ if (isAvailable() && lifecycle != null) {
lifecycle.addObserver(this);
}
}
diff --git a/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java
new file mode 100644
index 00000000..ba358f83
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java
@@ -0,0 +1,85 @@
+/*
+ * 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.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+/**
+ * Preference controller for bluetooth address
+ */
+public abstract class AbstractBluetoothAddressPreferenceController
+ extends AbstractConnectivityPreferenceController {
+
+ @VisibleForTesting
+ static final String KEY_BT_ADDRESS = "bt_address";
+
+ private static final String[] CONNECTIVITY_INTENTS = {
+ BluetoothAdapter.ACTION_STATE_CHANGED
+ };
+
+ private Preference mBtAddress;
+
+ public AbstractBluetoothAddressPreferenceController(Context context, Lifecycle lifecycle) {
+ super(context, lifecycle);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return BluetoothAdapter.getDefaultAdapter() != null;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_BT_ADDRESS;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mBtAddress = screen.findPreference(KEY_BT_ADDRESS);
+ updateConnectivity();
+ }
+
+ @Override
+ protected String[] getConnectivityIntents() {
+ return CONNECTIVITY_INTENTS;
+ }
+
+ @SuppressLint("HardwareIds")
+ @Override
+ protected void updateConnectivity() {
+ BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
+ if (bluetooth != null && mBtAddress != null) {
+ String address = bluetooth.isEnabled() ? bluetooth.getAddress() : null;
+ if (!TextUtils.isEmpty(address)) {
+ // Convert the address to lowercase for consistency with the wifi MAC address.
+ mBtAddress.setSummary(address.toLowerCase());
+ } else {
+ mBtAddress.setSummary(R.string.status_unavailable);
+ }
+ }
+ }
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java
new file mode 100644
index 00000000..c6552f77
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java
@@ -0,0 +1,114 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Base class for preference controllers which listen to connectivity broadcasts
+ */
+public abstract class AbstractConnectivityPreferenceController
+ extends AbstractPreferenceController implements LifecycleObserver, OnStart, OnStop {
+
+ private final BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (ArrayUtils.contains(getConnectivityIntents(), action)) {
+ getHandler().sendEmptyMessage(EVENT_UPDATE_CONNECTIVITY);
+ }
+ }
+ };
+
+ private static final int EVENT_UPDATE_CONNECTIVITY = 600;
+
+ private Handler mHandler;
+
+ public AbstractConnectivityPreferenceController(Context context, Lifecycle lifecycle) {
+ super(context);
+ if (lifecycle != null) {
+ lifecycle.addObserver(this);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ mContext.unregisterReceiver(mConnectivityReceiver);
+ }
+
+ @Override
+ public void onStart() {
+ final IntentFilter connectivityIntentFilter = new IntentFilter();
+ final String[] intents = getConnectivityIntents();
+ for (String intent : intents) {
+ connectivityIntentFilter.addAction(intent);
+ }
+
+ mContext.registerReceiver(mConnectivityReceiver, connectivityIntentFilter,
+ android.Manifest.permission.CHANGE_NETWORK_STATE, null);
+ }
+
+ protected abstract String[] getConnectivityIntents();
+
+ protected abstract void updateConnectivity();
+
+ private Handler getHandler() {
+ if (mHandler == null) {
+ mHandler = new ConnectivityEventHandler(this);
+ }
+ return mHandler;
+ }
+
+ private static class ConnectivityEventHandler extends Handler {
+ private WeakReference<AbstractConnectivityPreferenceController> mPreferenceController;
+
+ public ConnectivityEventHandler(AbstractConnectivityPreferenceController activity) {
+ mPreferenceController = new WeakReference<>(activity);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ AbstractConnectivityPreferenceController preferenceController
+ = mPreferenceController.get();
+ if (preferenceController == null) {
+ return;
+ }
+
+ switch (msg.what) {
+ case EVENT_UPDATE_CONNECTIVITY:
+ preferenceController.updateConnectivity();
+ break;
+ default:
+ throw new IllegalStateException("Unknown message " + msg.what);
+ }
+ }
+ }
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
new file mode 100644
index 00000000..bb8404b0
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.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.settingslib.deviceinfo;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.PersistableBundle;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+/**
+ * Preference controller for IMS status
+ */
+public abstract class AbstractImsStatusPreferenceController
+ extends AbstractConnectivityPreferenceController {
+
+ @VisibleForTesting
+ static final String KEY_IMS_REGISTRATION_STATE = "ims_reg_state";
+
+ private static final String[] CONNECTIVITY_INTENTS = {
+ BluetoothAdapter.ACTION_STATE_CHANGED,
+ ConnectivityManager.CONNECTIVITY_ACTION,
+ WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+ WifiManager.NETWORK_STATE_CHANGED_ACTION,
+ };
+
+ private Preference mImsStatus;
+
+ public AbstractImsStatusPreferenceController(Context context,
+ Lifecycle lifecycle) {
+ super(context, lifecycle);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class);
+ int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+ PersistableBundle config = null;
+ if (configManager != null) {
+ config = configManager.getConfigForSubId(subId);
+ }
+ return config != null && config.getBoolean(
+ CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_IMS_REGISTRATION_STATE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mImsStatus = screen.findPreference(KEY_IMS_REGISTRATION_STATE);
+ updateConnectivity();
+ }
+
+ @Override
+ protected String[] getConnectivityIntents() {
+ return CONNECTIVITY_INTENTS;
+ }
+
+ @Override
+ protected void updateConnectivity() {
+ int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+ if (mImsStatus != null) {
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ mImsStatus.setSummary((tm != null && tm.isImsRegistered(subId)) ?
+ R.string.ims_reg_status_registered : R.string.ims_reg_status_not_registered);
+ }
+ }
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
new file mode 100644
index 00000000..ded30226
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
@@ -0,0 +1,112 @@
+/*
+ * 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.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.wifi.WifiManager;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import java.net.InetAddress;
+import java.util.Iterator;
+
+/**
+ * Preference controller for IP address
+ */
+public abstract class AbstractIpAddressPreferenceController
+ extends AbstractConnectivityPreferenceController {
+
+ @VisibleForTesting
+ static final String KEY_IP_ADDRESS = "wifi_ip_address";
+
+ private static final String[] CONNECTIVITY_INTENTS = {
+ ConnectivityManager.CONNECTIVITY_ACTION,
+ WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+ WifiManager.NETWORK_STATE_CHANGED_ACTION,
+ };
+
+ private Preference mIpAddress;
+ private final ConnectivityManager mCM;
+
+ public AbstractIpAddressPreferenceController(Context context, Lifecycle lifecycle) {
+ super(context, lifecycle);
+ mCM = context.getSystemService(ConnectivityManager.class);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_IP_ADDRESS;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mIpAddress = screen.findPreference(KEY_IP_ADDRESS);
+ updateConnectivity();
+ }
+
+ @Override
+ protected String[] getConnectivityIntents() {
+ return CONNECTIVITY_INTENTS;
+ }
+
+ @Override
+ protected void updateConnectivity() {
+ String ipAddress = getDefaultIpAddresses(mCM);
+ if (ipAddress != null) {
+ mIpAddress.setSummary(ipAddress);
+ } else {
+ mIpAddress.setSummary(R.string.status_unavailable);
+ }
+ }
+
+ /**
+ * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style
+ * addresses.
+ * @param cm ConnectivityManager
+ * @return the formatted and newline-separated IP addresses, or null if none.
+ */
+ private static String getDefaultIpAddresses(ConnectivityManager cm) {
+ LinkProperties prop = cm.getActiveLinkProperties();
+ return formatIpAddresses(prop);
+ }
+
+ private static String formatIpAddresses(LinkProperties prop) {
+ if (prop == null) return null;
+ Iterator<InetAddress> iter = prop.getAllAddresses().iterator();
+ // If there are no entries, return null
+ if (!iter.hasNext()) return null;
+ // Concatenate all available addresses, newline separated
+ StringBuilder addresses = new StringBuilder();
+ while (iter.hasNext()) {
+ addresses.append(iter.next().getHostAddress());
+ if (iter.hasNext()) addresses.append("\n");
+ }
+ return addresses.toString();
+ }
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java b/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java
new file mode 100644
index 00000000..ac61ade1
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java
@@ -0,0 +1,119 @@
+/*
+ * 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.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.format.DateUtils;
+
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Preference controller for uptime
+ */
+public abstract class AbstractUptimePreferenceController extends AbstractPreferenceController
+ implements LifecycleObserver, OnStart, OnStop {
+
+ @VisibleForTesting
+ static final String KEY_UPTIME = "up_time";
+ private static final int EVENT_UPDATE_STATS = 500;
+
+ private Preference mUptime;
+ private Handler mHandler;
+
+ public AbstractUptimePreferenceController(Context context, Lifecycle lifecycle) {
+ super(context);
+ if (lifecycle != null) {
+ lifecycle.addObserver(this);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ getHandler().sendEmptyMessage(EVENT_UPDATE_STATS);
+ }
+
+ @Override
+ public void onStop() {
+ getHandler().removeMessages(EVENT_UPDATE_STATS);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_UPTIME;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mUptime = screen.findPreference(KEY_UPTIME);
+ updateTimes();
+ }
+
+ private Handler getHandler() {
+ if (mHandler == null) {
+ mHandler = new MyHandler(this);
+ }
+ return mHandler;
+ }
+
+ private void updateTimes() {
+ mUptime.setSummary(DateUtils.formatDuration(SystemClock.elapsedRealtime()));
+ }
+
+ private static class MyHandler extends Handler {
+ private WeakReference<AbstractUptimePreferenceController> mStatus;
+
+ public MyHandler(AbstractUptimePreferenceController activity) {
+ mStatus = new WeakReference<>(activity);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ AbstractUptimePreferenceController status = mStatus.get();
+ if (status == null) {
+ return;
+ }
+
+ switch (msg.what) {
+ case EVENT_UPDATE_STATS:
+ status.updateTimes();
+ sendEmptyMessageDelayed(EVENT_UPDATE_STATS, 1000);
+ break;
+
+ default:
+ throw new IllegalStateException("Unknown message " + msg.what);
+ }
+ }
+ }
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
new file mode 100644
index 00000000..d57b64f0
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
@@ -0,0 +1,88 @@
+/*
+ * 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.annotation.SuppressLint;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+/**
+ * Preference controller for WIFI MAC address
+ */
+public abstract class AbstractWifiMacAddressPreferenceController
+ extends AbstractConnectivityPreferenceController {
+
+ @VisibleForTesting
+ static final String KEY_WIFI_MAC_ADDRESS = "wifi_mac_address";
+
+ private static final String[] CONNECTIVITY_INTENTS = {
+ ConnectivityManager.CONNECTIVITY_ACTION,
+ WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+ WifiManager.NETWORK_STATE_CHANGED_ACTION,
+ };
+
+ private Preference mWifiMacAddress;
+ private final WifiManager mWifiManager;
+
+ public AbstractWifiMacAddressPreferenceController(Context context, Lifecycle lifecycle) {
+ super(context, lifecycle);
+ mWifiManager = context.getSystemService(WifiManager.class);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_WIFI_MAC_ADDRESS;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
+ updateConnectivity();
+ }
+
+ @Override
+ protected String[] getConnectivityIntents() {
+ return CONNECTIVITY_INTENTS;
+ }
+
+ @SuppressLint("HardwareIds")
+ @Override
+ protected void updateConnectivity() {
+ WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+ String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
+ if (!TextUtils.isEmpty(macAddress)) {
+ mWifiMacAddress.setSummary(macAddress);
+ } else {
+ mWifiMacAddress.setSummary(R.string.status_unavailable);
+ }
+ }
+}
diff --git a/com/android/settingslib/suggestions/SuggestionParser.java b/com/android/settingslib/suggestions/SuggestionParser.java
index 56b84415..9c347631 100644
--- a/com/android/settingslib/suggestions/SuggestionParser.java
+++ b/com/android/settingslib/suggestions/SuggestionParser.java
@@ -261,7 +261,7 @@ public class SuggestionParser {
if (requiredAccountType == null) {
return true;
}
- AccountManager accountManager = AccountManager.get(mContext);
+ AccountManager accountManager = mContext.getSystemService(AccountManager.class);
Account[] accounts = accountManager.getAccountsByType(requiredAccountType);
boolean satisfiesRequiredAccount = accounts.length > 0;
if (!satisfiesRequiredAccount) {
diff --git a/com/android/settingslib/wifi/WifiTracker.java b/com/android/settingslib/wifi/WifiTracker.java
index 12455d85..c56e1da1 100644
--- a/com/android/settingslib/wifi/WifiTracker.java
+++ b/com/android/settingslib/wifi/WifiTracker.java
@@ -291,15 +291,6 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
}
/**
- * Force a scan for wifi networks to happen now.
- */
- public void forceScan() {
- if (mWifiManager.isWifiEnabled() && mScanner != null) {
- mScanner.forceScan();
- }
- }
-
- /**
* Temporarily stop scanning for wifi networks.
*/
public void pauseScanning() {
@@ -789,14 +780,6 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
}
}
- public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
- boolean includeScans) {
- WifiTracker tracker = new WifiTracker(context, null, includeSaved, includeScans);
- tracker.forceUpdate();
- tracker.copyAndNotifyListeners(false /*notifyListeners*/);
- return tracker.getAccessPoints();
- }
-
@VisibleForTesting
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@@ -986,11 +969,6 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
}
}
- void forceScan() {
- removeMessages(MSG_SCAN);
- sendEmptyMessage(MSG_SCAN);
- }
-
void pause() {
mRetry = 0;
removeMessages(MSG_SCAN);
diff --git a/com/android/setupwizardlib/test/util/DrawingTestActivity.java b/com/android/setupwizardlib/test/util/DrawingTestActivity.java
index 3d11e128..154339a7 100644
--- a/com/android/setupwizardlib/test/util/DrawingTestActivity.java
+++ b/com/android/setupwizardlib/test/util/DrawingTestActivity.java
@@ -16,14 +16,15 @@
package com.android.setupwizardlib.test.util;
-import android.app.Activity;
+import android.support.v7.app.AppCompatActivity;
/**
* 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 activity, including any
- * injected layout inflater factories and custom themes etc.
+ * 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.
*
* @see DrawingTestHelper
*/
-public class DrawingTestActivity extends Activity {
+public class DrawingTestActivity extends AppCompatActivity {
}
diff --git a/com/android/setupwizardlib/util/WizardManagerHelper.java b/com/android/setupwizardlib/util/WizardManagerHelper.java
index a93694c1..896c0137 100644
--- a/com/android/setupwizardlib/util/WizardManagerHelper.java
+++ b/com/android/setupwizardlib/util/WizardManagerHelper.java
@@ -45,6 +45,8 @@ public class WizardManagerHelper {
static final String EXTRA_IS_FIRST_RUN = "firstRun";
@VisibleForTesting
static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup";
+ @VisibleForTesting
+ static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup";
public static final String EXTRA_THEME = "theme";
public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode";
@@ -213,6 +215,18 @@ public class WizardManagerHelper {
}
/**
+ * Checks whether an intent is running in "pre-deferred" setup wizard flow.
+ *
+ * @param originalIntent The original intent that was used to start the step, usually via
+ * {@link android.app.Activity#getIntent()}.
+ * @return true if the intent passed in was running in "pre-deferred" setup wizard.
+ */
+ public static boolean isPreDeferredSetupWizard(Intent originalIntent) {
+ return originalIntent != null
+ && originalIntent.getBooleanExtra(EXTRA_IS_PRE_DEFERRED_SETUP, false);
+ }
+
+ /**
* Checks the intent whether the extra indicates that the light theme should be used or not. If
* the theme is not specified in the intent, or the theme specified is unknown, the value def
* will be returned.
diff --git a/com/android/setupwizardlib/util/WizardManagerHelperTest.java b/com/android/setupwizardlib/util/WizardManagerHelperTest.java
index 4c460c8c..6477b518 100644
--- a/com/android/setupwizardlib/util/WizardManagerHelperTest.java
+++ b/com/android/setupwizardlib/util/WizardManagerHelperTest.java
@@ -88,6 +88,14 @@ public class WizardManagerHelperTest {
}
@Test
+ public void testIsPreDeferredSetupTrue() {
+ final Intent intent = new Intent();
+ intent.putExtra("preDeferredSetup", true);
+ assertTrue("Is pre-deferred setup wizard should be true",
+ WizardManagerHelper.isPreDeferredSetupWizard(intent));
+ }
+
+ @Test
public void testIsSetupWizardFalse() {
final Intent intent = new Intent();
intent.putExtra("firstRun", false);
diff --git a/com/android/setupwizardlib/view/NavigationBarButton.java b/com/android/setupwizardlib/view/NavigationBarButton.java
index 45d3737c..5172c476 100644
--- a/com/android/setupwizardlib/view/NavigationBarButton.java
+++ b/com/android/setupwizardlib/view/NavigationBarButton.java
@@ -17,16 +17,143 @@
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 5a78561f..e6bc9da0 100644
--- a/com/android/setupwizardlib/view/RichTextView.java
+++ b/com/android/setupwizardlib/view/RichTextView.java
@@ -17,6 +17,11 @@
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;
@@ -25,22 +30,18 @@ import android.text.style.ClickableSpan;
import android.text.style.TextAppearanceSpan;
import android.util.AttributeSet;
import android.util.Log;
-import android.widget.TextView;
+import android.view.MotionEvent;
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 TextView implements OnLinkClickListener {
+public class RichTextView extends AppCompatTextView implements OnLinkClickListener {
/* static section */
@@ -88,14 +89,22 @@ public class RichTextView extends TextView implements OnLinkClickListener {
/* 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
@@ -132,6 +141,32 @@ public class RichTextView extends TextView implements OnLinkClickListener {
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/Dependency.java b/com/android/systemui/Dependency.java
index 2937a250..d8a47c5e 100644
--- a/com/android/systemui/Dependency.java
+++ b/com/android/systemui/Dependency.java
@@ -22,6 +22,8 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import android.util.ArrayMap;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.NightDisplayController;
@@ -304,6 +306,8 @@ public class Dependency extends SystemUI {
mProviders.put(LightBarController.class, () -> new LightBarController(mContext));
+ mProviders.put(IWindowManager.class, () -> WindowManagerGlobal.getWindowManagerService());
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
}
diff --git a/com/android/systemui/ImageWallpaper.java b/com/android/systemui/ImageWallpaper.java
index 907a79e7..593bb508 100644
--- a/com/android/systemui/ImageWallpaper.java
+++ b/com/android/systemui/ImageWallpaper.java
@@ -16,11 +16,6 @@
package com.android.systemui;
-import static android.opengl.GLES20.*;
-
-import static javax.microedition.khronos.egl.EGL10.*;
-
-import android.app.ActivityManager;
import android.app.WallpaperManager;
import android.content.ComponentCallbacks2;
import android.graphics.Bitmap;
@@ -28,31 +23,18 @@ import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region.Op;
-import android.opengl.GLUtils;
import android.os.AsyncTask;
-import android.os.SystemProperties;
import android.os.Trace;
-import android.renderscript.Matrix4f;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.Display;
import android.view.DisplayInfo;
-import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.WindowManager;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
/**
* Default built-in wallpaper that simply shows a static image.
@@ -64,24 +46,13 @@ public class ImageWallpaper extends WallpaperService {
private static final boolean DEBUG = false;
private static final String PROPERTY_KERNEL_QEMU = "ro.kernel.qemu";
- static final boolean FIXED_SIZED_SURFACE = true;
- static final boolean USE_OPENGL = true;
-
- WallpaperManager mWallpaperManager;
-
- DrawableEngine mEngine;
-
- boolean mIsHwAccelerated;
+ private WallpaperManager mWallpaperManager;
+ private DrawableEngine mEngine;
@Override
public void onCreate() {
super.onCreate();
- mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
-
- //noinspection PointlessBooleanExpression,ConstantConditions
- if (FIXED_SIZED_SURFACE && USE_OPENGL) {
- mIsHwAccelerated = ActivityManager.isHighEndGfx();
- }
+ mWallpaperManager = getSystemService(WallpaperManager.class);
}
@Override
@@ -98,15 +69,12 @@ public class ImageWallpaper extends WallpaperService {
}
class DrawableEngine extends Engine {
- static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
- static final int EGL_OPENGL_ES2_BIT = 4;
-
Bitmap mBackground;
int mBackgroundWidth = -1, mBackgroundHeight = -1;
int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
int mLastRotation = -1;
- float mXOffset = 0.5f;
- float mYOffset = 0.5f;
+ float mXOffset = 0f;
+ float mYOffset = 0f;
float mScale = 1f;
private Display mDefaultDisplay;
@@ -117,34 +85,6 @@ public class ImageWallpaper extends WallpaperService {
int mLastXTranslation;
int mLastYTranslation;
- private EGL10 mEgl;
- private EGLDisplay mEglDisplay;
- private EGLConfig mEglConfig;
- private EGLContext mEglContext;
- private EGLSurface mEglSurface;
-
- private static final String sSimpleVS =
- "attribute vec4 position;\n" +
- "attribute vec2 texCoords;\n" +
- "varying vec2 outTexCoords;\n" +
- "uniform mat4 projection;\n" +
- "\nvoid main(void) {\n" +
- " outTexCoords = texCoords;\n" +
- " gl_Position = projection * position;\n" +
- "}\n\n";
- private static final String sSimpleFS =
- "precision mediump float;\n\n" +
- "varying vec2 outTexCoords;\n" +
- "uniform sampler2D texture;\n" +
- "\nvoid main(void) {\n" +
- " gl_FragColor = texture2D(texture, outTexCoords);\n" +
- "}\n\n";
-
- private static final int FLOAT_SIZE_BYTES = 4;
- private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
- private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
- private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
-
private int mRotationAtLastSurfaceSizeUpdate = -1;
private int mDisplayWidthAtLastSurfaceSizeUpdate = -1;
private int mDisplayHeightAtLastSurfaceSizeUpdate = -1;
@@ -154,13 +94,14 @@ public class ImageWallpaper extends WallpaperService {
private AsyncTask<Void, Void, Bitmap> mLoader;
private boolean mNeedsDrawAfterLoadingWallpaper;
private boolean mSurfaceValid;
+ private boolean mSurfaceRedrawNeeded;
- public DrawableEngine() {
+ DrawableEngine() {
super();
setFixedSizeAllowed(true);
}
- public void trimMemory(int level) {
+ void trimMemory(int level) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
&& level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
&& mBackground != null) {
@@ -179,6 +120,7 @@ public class ImageWallpaper extends WallpaperService {
super.onCreate(surfaceHolder);
+ //noinspection ConstantConditions
mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
setOffsetNotificationsEnabled(false);
@@ -199,7 +141,7 @@ public class ImageWallpaper extends WallpaperService {
// Load background image dimensions, if we haven't saved them yet
if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
// Need to load the image to get dimensions
- loadWallpaper(forDraw, false /* needsReset */);
+ loadWallpaper(forDraw);
if (DEBUG) {
Log.d(TAG, "Reloading, redoing updateSurfaceSize later.");
}
@@ -210,16 +152,13 @@ public class ImageWallpaper extends WallpaperService {
int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);
- if (FIXED_SIZED_SURFACE) {
- // Used a fixed size surface, because we are special. We can do
- // this because we know the current design of window animations doesn't
- // cause this to break.
- surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
- mLastRequestedWidth = surfaceWidth;
- mLastRequestedHeight = surfaceHeight;
- } else {
- surfaceHolder.setSizeFromLayout();
- }
+ // Used a fixed size surface, because we are special. We can do
+ // this because we know the current design of window animations doesn't
+ // cause this to break.
+ surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
+ mLastRequestedWidth = surfaceWidth;
+ mLastRequestedHeight = surfaceHeight;
+
return hasWallpaper;
}
@@ -300,6 +239,13 @@ public class ImageWallpaper extends WallpaperService {
Log.d(TAG, "onSurfaceRedrawNeeded");
}
super.onSurfaceRedrawNeeded(holder);
+ // At the end of this method we should have drawn into the surface.
+ // This means that the bitmap should be loaded synchronously if
+ // it was already unloaded.
+ if (mBackground == null) {
+ updateBitmap(mWallpaperManager.getBitmap(true /* hardware */));
+ }
+ mSurfaceRedrawNeeded = true;
drawFrame();
}
@@ -336,7 +282,8 @@ public class ImageWallpaper extends WallpaperService {
boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth
|| dh != mLastSurfaceHeight;
- boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation;
+ boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation
+ || mSurfaceRedrawNeeded;
if (!redrawNeeded && !mOffsetsChanged) {
if (DEBUG) {
Log.d(TAG, "Suppressed drawFrame since redraw is not needed "
@@ -345,40 +292,24 @@ public class ImageWallpaper extends WallpaperService {
return;
}
mLastRotation = newRotation;
+ mSurfaceRedrawNeeded = false;
// Load bitmap if it is not yet loaded
if (mBackground == null) {
- if (DEBUG) {
- Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = " +
- mBackground + ", " +
- ((mBackground == null) ? 0 : mBackground.getWidth()) + ", " +
- ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " +
- dw + ", " + dh);
- }
- loadWallpaper(true /* needDraw */, true /* needReset */);
+ loadWallpaper(true);
if (DEBUG) {
Log.d(TAG, "Reloading, resuming draw later");
}
return;
}
- // Center the scaled image
+ // Left align the scaled image
mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(),
dh / (float) mBackground.getHeight()));
- final int availw = dw - (int) (mBackground.getWidth() * mScale);
- final int availh = dh - (int) (mBackground.getHeight() * mScale);
- int xPixels = availw / 2;
- int yPixels = availh / 2;
-
- // Adjust the image for xOffset/yOffset values. If window manager is handling offsets,
- // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels
- // will remain unchanged
- final int availwUnscaled = dw - mBackground.getWidth();
- final int availhUnscaled = dh - mBackground.getHeight();
- if (availwUnscaled < 0)
- xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f);
- if (availhUnscaled < 0)
- yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f);
+ final int availw = (int) (mBackground.getWidth() * mScale) - dw;
+ final int availh = (int) (mBackground.getHeight() * mScale) - dh;
+ int xPixels = (int) (availw * mXOffset);
+ int yPixels = (int) (availh * mYOffset);
mOffsetsChanged = false;
if (surfaceDimensionsChanged) {
@@ -399,21 +330,7 @@ public class ImageWallpaper extends WallpaperService {
Log.d(TAG, "Redrawing wallpaper");
}
- if (mIsHwAccelerated) {
- if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
- drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
- }
- } else {
- drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
- if (FIXED_SIZED_SURFACE) {
- // If the surface is fixed-size, we should only need to
- // draw it once and then we'll let the window manager
- // position it appropriately. As such, we no longer needed
- // the loaded bitmap. Yay!
- // hw-accelerated renderer retains bitmap for faster rotation
- unloadWallpaper(false /* forgetSize */);
- }
- }
+ drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
@@ -428,28 +345,20 @@ public class ImageWallpaper extends WallpaperService {
*
* If {@param needsReset} is set also clears the cache in WallpaperManager first.
*/
- private void loadWallpaper(boolean needsDraw, boolean needsReset) {
+ private void loadWallpaper(boolean needsDraw) {
mNeedsDrawAfterLoadingWallpaper |= needsDraw;
if (mLoader != null) {
- if (needsReset) {
- mLoader.cancel(false /* interrupt */);
- mLoader = null;
- } else {
- if (DEBUG) {
- Log.d(TAG, "Skipping loadWallpaper, already in flight ");
- }
- return;
+ if (DEBUG) {
+ Log.d(TAG, "Skipping loadWallpaper, already in flight ");
}
+ return;
}
mLoader = new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
Throwable exception;
try {
- if (needsReset) {
- mWallpaperManager.forgetLoadedWallpaper();
- }
- return mWallpaperManager.getBitmap();
+ return mWallpaperManager.getBitmap(true /* hardware */);
} catch (RuntimeException | OutOfMemoryError e) {
exception = e;
}
@@ -458,48 +367,33 @@ public class ImageWallpaper extends WallpaperService {
return null;
}
- if (exception != null) {
- // Note that if we do fail at this, and the default wallpaper can't
- // be loaded, we will go into a cycle. Don't do a build where the
- // default wallpaper can't be loaded.
- Log.w(TAG, "Unable to load wallpaper!", exception);
- try {
- mWallpaperManager.clear();
- } catch (IOException ex) {
- // now we're really screwed.
- Log.w(TAG, "Unable reset to default wallpaper!", ex);
- }
-
- if (isCancelled()) {
- return null;
- }
-
- try {
- return mWallpaperManager.getBitmap();
- } catch (RuntimeException | OutOfMemoryError e) {
- Log.w(TAG, "Unable to load default wallpaper!", e);
- }
+ // Note that if we do fail at this, and the default wallpaper can't
+ // be loaded, we will go into a cycle. Don't do a build where the
+ // default wallpaper can't be loaded.
+ Log.w(TAG, "Unable to load wallpaper!", exception);
+ try {
+ mWallpaperManager.clear();
+ } catch (IOException ex) {
+ // now we're really screwed.
+ Log.w(TAG, "Unable reset to default wallpaper!", ex);
+ }
+
+ if (isCancelled()) {
+ return null;
+ }
+
+ try {
+ return mWallpaperManager.getBitmap(true /* hardware */);
+ } catch (RuntimeException | OutOfMemoryError e) {
+ Log.w(TAG, "Unable to load default wallpaper!", e);
}
return null;
}
@Override
protected void onPostExecute(Bitmap b) {
- mBackground = null;
- mBackgroundWidth = -1;
- mBackgroundHeight = -1;
-
- if (b != null) {
- mBackground = b;
- mBackgroundWidth = mBackground.getWidth();
- mBackgroundHeight = mBackground.getHeight();
- }
+ updateBitmap(b);
- if (DEBUG) {
- Log.d(TAG, "Wallpaper loaded: " + mBackground);
- }
- updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
- false /* forDraw */);
if (mNeedsDrawAfterLoadingWallpaper) {
drawFrame();
}
@@ -510,6 +404,24 @@ public class ImageWallpaper extends WallpaperService {
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
+ private void updateBitmap(Bitmap bitmap) {
+ mBackground = null;
+ mBackgroundWidth = -1;
+ mBackgroundHeight = -1;
+
+ if (bitmap != null) {
+ mBackground = bitmap;
+ mBackgroundWidth = mBackground.getWidth();
+ mBackgroundHeight = mBackground.getHeight();
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Wallpaper loaded: " + mBackground);
+ }
+ updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
+ false /* forDraw */);
+ }
+
private void unloadWallpaper(boolean forgetSize) {
if (mLoader != null) {
mLoader.cancel(false);
@@ -564,7 +476,7 @@ public class ImageWallpaper extends WallpaperService {
}
private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) {
- Canvas c = sh.lockCanvas();
+ Canvas c = sh.lockHardwareCanvas();
if (c != null) {
try {
if (DEBUG) {
@@ -590,278 +502,5 @@ public class ImageWallpaper extends WallpaperService {
}
}
}
-
- private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) {
- if (!initGL(sh)) return false;
-
- final float right = left + mBackground.getWidth() * mScale;
- final float bottom = top + mBackground.getHeight() * mScale;
-
- final Rect frame = sh.getSurfaceFrame();
- final Matrix4f ortho = new Matrix4f();
- ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f);
-
- final FloatBuffer triangleVertices = createMesh(left, top, right, bottom);
-
- final int texture = loadTexture(mBackground);
- final int program = buildProgram(sSimpleVS, sSimpleFS);
-
- final int attribPosition = glGetAttribLocation(program, "position");
- final int attribTexCoords = glGetAttribLocation(program, "texCoords");
- final int uniformTexture = glGetUniformLocation(program, "texture");
- final int uniformProjection = glGetUniformLocation(program, "projection");
-
- checkGlError();
-
- glViewport(0, 0, frame.width(), frame.height());
- glBindTexture(GL_TEXTURE_2D, texture);
-
- glUseProgram(program);
- glEnableVertexAttribArray(attribPosition);
- glEnableVertexAttribArray(attribTexCoords);
- glUniform1i(uniformTexture, 0);
- glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0);
-
- checkGlError();
-
- if (w > 0 || h > 0) {
- glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- }
-
- // drawQuad
- triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
- glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
- TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-
- triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
- glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
- TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-
- boolean status = mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
- checkEglError();
-
- finishGL(texture, program);
-
- return status;
- }
-
- private FloatBuffer createMesh(int left, int top, float right, float bottom) {
- final float[] verticesData = {
- // X, Y, Z, U, V
- left, bottom, 0.0f, 0.0f, 1.0f,
- right, bottom, 0.0f, 1.0f, 1.0f,
- left, top, 0.0f, 0.0f, 0.0f,
- right, top, 0.0f, 1.0f, 0.0f,
- };
-
- final int bytes = verticesData.length * FLOAT_SIZE_BYTES;
- final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order(
- ByteOrder.nativeOrder()).asFloatBuffer();
- triangleVertices.put(verticesData).position(0);
- return triangleVertices;
- }
-
- private int loadTexture(Bitmap bitmap) {
- int[] textures = new int[1];
-
- glActiveTexture(GL_TEXTURE0);
- glGenTextures(1, textures, 0);
- checkGlError();
-
- int texture = textures[0];
- glBindTexture(GL_TEXTURE_2D, texture);
- checkGlError();
-
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
- GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
- checkGlError();
-
- return texture;
- }
-
- private int buildProgram(String vertex, String fragment) {
- int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
- if (vertexShader == 0) return 0;
-
- int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
- if (fragmentShader == 0) return 0;
-
- int program = glCreateProgram();
- glAttachShader(program, vertexShader);
- glAttachShader(program, fragmentShader);
- glLinkProgram(program);
- checkGlError();
-
- glDeleteShader(vertexShader);
- glDeleteShader(fragmentShader);
-
- int[] status = new int[1];
- glGetProgramiv(program, GL_LINK_STATUS, status, 0);
- if (status[0] != GL_TRUE) {
- String error = glGetProgramInfoLog(program);
- Log.d(GL_LOG_TAG, "Error while linking program:\n" + error);
- glDeleteProgram(program);
- return 0;
- }
-
- return program;
- }
-
- private int buildShader(String source, int type) {
- int shader = glCreateShader(type);
-
- glShaderSource(shader, source);
- checkGlError();
-
- glCompileShader(shader);
- checkGlError();
-
- int[] status = new int[1];
- glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
- if (status[0] != GL_TRUE) {
- String error = glGetShaderInfoLog(shader);
- Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error);
- glDeleteShader(shader);
- return 0;
- }
-
- return shader;
- }
-
- private void checkEglError() {
- int error = mEgl.eglGetError();
- if (error != EGL_SUCCESS) {
- Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error));
- }
- }
-
- private void checkGlError() {
- int error = glGetError();
- if (error != GL_NO_ERROR) {
- Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable());
- }
- }
-
- private void finishGL(int texture, int program) {
- int[] textures = new int[1];
- textures[0] = texture;
- glDeleteTextures(1, textures, 0);
- glDeleteProgram(program);
- mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
- mEgl.eglDestroyContext(mEglDisplay, mEglContext);
- mEgl.eglTerminate(mEglDisplay);
- }
-
- private boolean initGL(SurfaceHolder surfaceHolder) {
- mEgl = (EGL10) EGLContext.getEGL();
-
- mEglDisplay = mEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
- if (mEglDisplay == EGL_NO_DISPLAY) {
- throw new RuntimeException("eglGetDisplay failed " +
- GLUtils.getEGLErrorString(mEgl.eglGetError()));
- }
-
- int[] version = new int[2];
- if (!mEgl.eglInitialize(mEglDisplay, version)) {
- throw new RuntimeException("eglInitialize failed " +
- GLUtils.getEGLErrorString(mEgl.eglGetError()));
- }
-
- mEglConfig = chooseEglConfig();
- if (mEglConfig == null) {
- throw new RuntimeException("eglConfig not initialized");
- }
-
- mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
- if (mEglContext == EGL_NO_CONTEXT) {
- throw new RuntimeException("createContext failed " +
- GLUtils.getEGLErrorString(mEgl.eglGetError()));
- }
-
- int attribs[] = {
- EGL_WIDTH, 1,
- EGL_HEIGHT, 1,
- EGL_NONE
- };
- EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
- mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext);
-
- int[] maxSize = new int[1];
- Rect frame = surfaceHolder.getSurfaceFrame();
- glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0);
-
- mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- mEgl.eglDestroySurface(mEglDisplay, tmpSurface);
-
- if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) {
- mEgl.eglDestroyContext(mEglDisplay, mEglContext);
- mEgl.eglTerminate(mEglDisplay);
- Log.e(GL_LOG_TAG, "requested texture size " +
- frame.width() + "x" + frame.height() + " exceeds the support maximum of " +
- maxSize[0] + "x" + maxSize[0]);
- return false;
- }
-
- mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null);
- if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
- int error = mEgl.eglGetError();
- if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) {
- Log.e(GL_LOG_TAG, "createWindowSurface returned " +
- GLUtils.getEGLErrorString(error) + ".");
- return false;
- }
- throw new RuntimeException("createWindowSurface failed " +
- GLUtils.getEGLErrorString(error));
- }
-
- if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
- throw new RuntimeException("eglMakeCurrent failed " +
- GLUtils.getEGLErrorString(mEgl.eglGetError()));
- }
-
- return true;
- }
-
-
- EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
- int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
- return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attrib_list);
- }
-
- private EGLConfig chooseEglConfig() {
- int[] configsCount = new int[1];
- EGLConfig[] configs = new EGLConfig[1];
- int[] configSpec = getConfig();
- if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
- throw new IllegalArgumentException("eglChooseConfig failed " +
- GLUtils.getEGLErrorString(mEgl.eglGetError()));
- } else if (configsCount[0] > 0) {
- return configs[0];
- }
- return null;
- }
-
- private int[] getConfig() {
- return new int[] {
- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL_RED_SIZE, 8,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_ALPHA_SIZE, 0,
- EGL_DEPTH_SIZE, 0,
- EGL_STENCIL_SIZE, 0,
- EGL_CONFIG_CAVEAT, EGL_NONE,
- EGL_NONE
- };
- }
}
}
diff --git a/com/android/systemui/SwipeHelper.java b/com/android/systemui/SwipeHelper.java
index 4b377153..592dda07 100644
--- a/com/android/systemui/SwipeHelper.java
+++ b/com/android/systemui/SwipeHelper.java
@@ -83,7 +83,6 @@ public class SwipeHelper implements Gefingerpoken {
private boolean mMenuRowIntercepting;
private boolean mLongPressSent;
- private LongPressListener mLongPressListener;
private Runnable mWatchLongPress;
private final long mLongPressTimeout;
@@ -115,10 +114,6 @@ public class SwipeHelper implements Gefingerpoken {
mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
}
- public void setLongPressListener(LongPressListener listener) {
- mLongPressListener = listener;
- }
-
public void setDensityScale(float densityScale) {
mDensityScale = densityScale;
}
@@ -257,7 +252,7 @@ public class SwipeHelper implements Gefingerpoken {
}
}
- public void removeLongPressCallback() {
+ public void cancelLongPress() {
if (mWatchLongPress != null) {
mHandler.removeCallbacks(mWatchLongPress);
mWatchLongPress = null;
@@ -288,33 +283,27 @@ public class SwipeHelper implements Gefingerpoken {
mInitialTouchPos = getPos(ev);
mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
mTranslation = getTranslation(mCurrView);
- if (mLongPressListener != null) {
- if (mWatchLongPress == null) {
- mWatchLongPress = new Runnable() {
- @Override
- public void run() {
- if (mCurrView != null && !mLongPressSent) {
- mLongPressSent = true;
+ if (mWatchLongPress == null) {
+ mWatchLongPress = new Runnable() {
+ @Override
+ public void run() {
+ if (mCurrView != null && !mLongPressSent) {
+ mLongPressSent = true;
+ mCurrView.getLocationOnScreen(mTmpPos);
+ final int x = (int) ev.getRawX() - mTmpPos[0];
+ final int y = (int) ev.getRawY() - mTmpPos[1];
+ if (mCurrView instanceof ExpandableNotificationRow) {
mCurrView.sendAccessibilityEvent(
AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
- mCurrView.getLocationOnScreen(mTmpPos);
- final int x = (int) ev.getRawX() - mTmpPos[0];
- final int y = (int) ev.getRawY() - mTmpPos[1];
- MenuItem menuItem = null;
- if (mCurrView instanceof ExpandableNotificationRow) {
- menuItem = ((ExpandableNotificationRow) mCurrView)
- .getProvider().getLongpressMenuItem(mContext);
- }
- if (menuItem != null) {
- mLongPressListener.onLongPress(mCurrView, x, y,
- menuItem);
- }
+ ExpandableNotificationRow currRow =
+ (ExpandableNotificationRow) mCurrView;
+ currRow.doLongClickCallback(x, y);
}
}
- };
- }
- mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
+ }
+ };
}
+ mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
}
break;
@@ -331,7 +320,7 @@ public class SwipeHelper implements Gefingerpoken {
mDragging = true;
mInitialTouchPos = getPos(ev);
mTranslation = getTranslation(mCurrView);
- removeLongPressCallback();
+ cancelLongPress();
}
}
break;
@@ -343,7 +332,7 @@ public class SwipeHelper implements Gefingerpoken {
mCurrView = null;
mLongPressSent = false;
mMenuRowIntercepting = false;
- removeLongPressCallback();
+ cancelLongPress();
if (captured) return true;
break;
}
@@ -586,7 +575,7 @@ public class SwipeHelper implements Gefingerpoken {
// We are not doing anything, make sure the long press callback
// is not still ticking like a bomb waiting to go off.
- removeLongPressCallback();
+ cancelLongPress();
return false;
}
}
@@ -734,15 +723,4 @@ public class SwipeHelper implements Gefingerpoken {
*/
float getFalsingThresholdFactor();
}
-
- /**
- * Equivalent to View.OnLongClickListener with coordinates
- */
- public interface LongPressListener {
- /**
- * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
- * @return whether the longpress was handled
- */
- boolean onLongPress(View v, int x, int y, MenuItem item);
- }
}
diff --git a/com/android/systemui/doze/DozeUi.java b/com/android/systemui/doze/DozeUi.java
index dc626fb4..851b78cf 100644
--- a/com/android/systemui/doze/DozeUi.java
+++ b/com/android/systemui/doze/DozeUi.java
@@ -84,9 +84,6 @@ public class DozeUi implements DozeMachine.Part {
case DOZE_REQUEST_PULSE:
pulseWhileDozing(mMachine.getPulseReason());
break;
- case DOZE_PULSE_DONE:
- mHost.abortPulsing();
- break;
case INITIALIZED:
mHost.startDozing();
break;
diff --git a/com/android/systemui/globalactions/GlobalActionsComponent.java b/com/android/systemui/globalactions/GlobalActionsComponent.java
index 09a08f09..f06cda0f 100644
--- a/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -31,6 +31,7 @@ import android.os.ServiceManager;
public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
+ private GlobalActions mPlugin;
private Extension<GlobalActions> mExtension;
private IStatusBarService mBarService;
@@ -41,10 +42,19 @@ public class GlobalActionsComponent extends SystemUI implements Callbacks, Globa
mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class)
.withPlugin(GlobalActions.class)
.withDefault(() -> new GlobalActionsImpl(mContext))
+ .withCallback(this::onExtensionCallback)
.build();
+ mPlugin = mExtension.get();
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
}
+ private void onExtensionCallback(GlobalActions newPlugin) {
+ if (mPlugin != null) {
+ mPlugin.destroy();
+ }
+ mPlugin = newPlugin;
+ }
+
@Override
public void handleShowShutdownUi(boolean isReboot, String reason) {
mExtension.get().showShutdownUi(isReboot, reason);
diff --git a/com/android/systemui/globalactions/GlobalActionsDialog.java b/com/android/systemui/globalactions/GlobalActionsDialog.java
index 189badfc..d82f9cd4 100644
--- a/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -16,27 +16,6 @@ package com.android.systemui.globalactions;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import com.android.internal.R;
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.colorextraction.ColorExtractor.GradientColors;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.EmergencyAffordanceManager;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.HardwareUiLayout;
-import com.android.systemui.Interpolators;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
-import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator;
-import com.android.systemui.volume.VolumeDialogMotion.LogDecelerateInterpolator;
-
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.ActivityManager;
import android.app.Dialog;
import android.app.WallpaperManager;
@@ -69,7 +48,6 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
-import android.util.MathUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
@@ -86,7 +64,23 @@ import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.internal.R;
+import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.colorextraction.drawable.GradientDrawable;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.util.EmergencyAffordanceManager;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
+import com.android.systemui.Interpolators;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator;
import java.util.ArrayList;
import java.util.List;
@@ -96,7 +90,8 @@ import java.util.List;
* may show depending on whether the keyguard is showing, and whether the device
* is provisioned.
*/
-class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+class GlobalActionsDialog implements DialogInterface.OnDismissListener,
+ DialogInterface.OnClickListener {
static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
@@ -195,6 +190,14 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
}
}
+ /**
+ * Dismiss the global actions dialog, if it's currently shown
+ */
+ public void dismissDialog() {
+ mHandler.removeMessages(MESSAGE_DISMISS);
+ mHandler.sendEmptyMessage(MESSAGE_DISMISS);
+ }
+
private void awakenIfNecessary() {
if (mDreamManager != null) {
try {
diff --git a/com/android/systemui/globalactions/GlobalActionsImpl.java b/com/android/systemui/globalactions/GlobalActionsImpl.java
index 2cf230c8..35634374 100644
--- a/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -14,12 +14,12 @@
package com.android.systemui.globalactions;
+import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
+
import android.app.Dialog;
import android.app.KeyguardManager;
-import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.Context;
-import android.graphics.Color;
import android.graphics.Point;
import android.view.ViewGroup;
import android.view.Window;
@@ -32,12 +32,14 @@ import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.colorextraction.drawable.GradientDrawable;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.GlobalActions;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
-public class GlobalActionsImpl implements GlobalActions {
+public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks {
private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f;
@@ -45,15 +47,23 @@ public class GlobalActionsImpl implements GlobalActions {
private final KeyguardMonitor mKeyguardMonitor;
private final DeviceProvisionedController mDeviceProvisionedController;
private GlobalActionsDialog mGlobalActions;
+ private boolean mDisabled;
public GlobalActionsImpl(Context context) {
mContext = context;
mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+ SysUiServiceProvider.getComponent(context, CommandQueue.class).addCallbacks(this);
+ }
+
+ @Override
+ public void destroy() {
+ SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallbacks(this);
}
@Override
public void showGlobalActions(GlobalActionsManager manager) {
+ if (mDisabled) return;
if (mGlobalActions == null) {
mGlobalActions = new GlobalActionsDialog(mContext, manager);
}
@@ -107,4 +117,14 @@ public class GlobalActionsImpl implements GlobalActions {
d.show();
}
+
+ @Override
+ public void disable(int state1, int state2, boolean animate) {
+ final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
+ if (disabled == mDisabled) return;
+ mDisabled = disabled;
+ if (disabled && mGlobalActions != null) {
+ mGlobalActions.dismissDialog();
+ }
+ }
}
diff --git a/com/android/systemui/keyguard/WorkLockActivityController.java b/com/android/systemui/keyguard/WorkLockActivityController.java
index f198229a..4c3d5bad 100644
--- a/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard;
-import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.IActivityManager;
@@ -29,9 +28,8 @@ import android.os.RemoteException;
import android.os.UserHandle;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
public class WorkLockActivityController {
private final Context mContext;
@@ -98,7 +96,7 @@ public class WorkLockActivityController {
}
}
- private final TaskStackListener mLockListener = new TaskStackListener() {
+ private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() {
@Override
public void onTaskProfileLocked(int taskId, int userId) {
startWorkChallengeInTask(taskId, userId);
diff --git a/com/android/systemui/pip/phone/InputConsumerController.java b/com/android/systemui/pip/phone/InputConsumerController.java
index abc56672..e6d6c558 100644
--- a/com/android/systemui/pip/phone/InputConsumerController.java
+++ b/com/android/systemui/pip/phone/InputConsumerController.java
@@ -28,8 +28,6 @@ import android.view.InputEvent;
import android.view.IWindowManager;
import android.view.MotionEvent;
-import com.android.systemui.recents.misc.Utilities;
-
import java.io.PrintWriter;
/**
diff --git a/com/android/systemui/pip/phone/PipManager.java b/com/android/systemui/pip/phone/PipManager.java
index f8996aae..7e87666a 100644
--- a/com/android/systemui/pip/phone/PipManager.java
+++ b/com/android/systemui/pip/phone/PipManager.java
@@ -41,8 +41,7 @@ import com.android.systemui.pip.BasePipManager;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.component.ExpandPipEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
-import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
import java.io.PrintWriter;
@@ -70,7 +69,7 @@ public class PipManager implements BasePipManager {
/**
* Handler for system task stack changes.
*/
- TaskStackListener mTaskStackListener = new TaskStackListener() {
+ TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
mTouchHandler.onActivityPinned();
diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java
index e0445c16..312b9908 100644
--- a/com/android/systemui/pip/tv/PipManager.java
+++ b/com/android/systemui/pip/tv/PipManager.java
@@ -20,7 +20,6 @@ import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
-import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -47,7 +46,7 @@ import android.view.WindowManagerGlobal;
import com.android.systemui.R;
import com.android.systemui.pip.BasePipManager;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -621,7 +620,7 @@ public class PipManager implements BasePipManager {
return false;
}
- private TaskStackListener mTaskStackListener = new TaskStackListener() {
+ private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
diff --git a/com/android/systemui/plugins/GlobalActions.java b/com/android/systemui/plugins/GlobalActions.java
index 1f633da4..95ff13b4 100644
--- a/com/android/systemui/plugins/GlobalActions.java
+++ b/com/android/systemui/plugins/GlobalActions.java
@@ -29,6 +29,9 @@ public interface GlobalActions extends Plugin {
default void showShutdownUi(boolean isReboot, String reason) {
}
+ default void destroy() {
+ }
+
@ProvidesInterface(version = GlobalActionsManager.VERSION)
public interface GlobalActionsManager {
int VERSION = 1;
diff --git a/com/android/systemui/power/PowerUI.java b/com/android/systemui/power/PowerUI.java
index f3782685..c1a36239 100644
--- a/com/android/systemui/power/PowerUI.java
+++ b/com/android/systemui/power/PowerUI.java
@@ -28,8 +28,14 @@ import android.database.ContentObserver;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.HardwarePropertiesManager;
+import android.os.IBinder;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.Temperature;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.format.DateUtils;
@@ -75,6 +81,7 @@ public class PowerUI extends SystemUI {
private float[] mRecentTemps = new float[MAX_RECENT_TEMPS];
private int mNumTemps;
private long mNextLogTime;
+ private IThermalService mThermalService;
// We create a method reference here so that we are guaranteed that we can remove a callback
// by using the same instance (method references are not guaranteed to be the same object
@@ -263,7 +270,7 @@ public class PowerUI extends SystemUI {
resources.getInteger(R.integer.config_warningTemperature));
if (mThresholdTemp < 0f) {
- // Get the throttling temperature. No need to check if we're not throttling.
+ // Get the shutdown temperature, adjust for warning tolerance.
float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures(
HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
HardwarePropertiesManager.TEMPERATURE_SHUTDOWN);
@@ -276,6 +283,25 @@ public class PowerUI extends SystemUI {
resources.getInteger(R.integer.config_warningTemperatureTolerance);
}
+ if (mThermalService == null) {
+ // Enable push notifications of throttling from vendor thermal
+ // management subsystem via thermalservice, in addition to our
+ // usual polling, to react to temperature jumps more quickly.
+ IBinder b = ServiceManager.getService("thermalservice");
+
+ if (b != null) {
+ mThermalService = IThermalService.Stub.asInterface(b);
+ try {
+ mThermalService.registerThermalEventListener(
+ new ThermalEventListener());
+ } catch (RemoteException e) {
+ // Should never happen.
+ }
+ } else {
+ Slog.w(TAG, "cannot find thermalservice, no throttling push notifications");
+ }
+ }
+
setNextLogTime();
// This initialization method may be called on a configuration change. Only one set of
@@ -414,5 +440,15 @@ public class PowerUI extends SystemUI {
void dump(PrintWriter pw);
void userSwitched();
}
-}
+ // Thermal event received from vendor thermal management subsystem
+ private final class ThermalEventListener extends IThermalEventListener.Stub {
+ @Override public void notifyThrottling(boolean isThrottling, Temperature temp) {
+ // Trigger an update of the temperature warning. Only one
+ // callback can be enabled at a time, so remove any existing
+ // callback; updateTemperatureWarning will schedule another one.
+ mHandler.removeCallbacks(mUpdateTempCallback);
+ updateTemperatureWarning();
+ }
+ }
+}
diff --git a/com/android/systemui/recents/Recents.java b/com/android/systemui/recents/Recents.java
index 283ac0c4..ce1438a1 100644
--- a/com/android/systemui/recents/Recents.java
+++ b/com/android/systemui/recents/Recents.java
@@ -29,14 +29,13 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
-import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.EventLog;
@@ -53,6 +52,7 @@ import com.android.systemui.RecentsComponent;
import com.android.systemui.SystemUI;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
@@ -62,7 +62,7 @@ import com.android.systemui.recents.events.component.SetWaitingForTransitionStar
import com.android.systemui.recents.events.component.ShowUserToastEvent;
import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
@@ -81,23 +81,15 @@ public class Recents extends SystemUI
implements RecentsComponent, CommandQueue.Callbacks {
private final static String TAG = "Recents";
- private final static boolean DEBUG = false;
public final static int EVENT_BUS_PRIORITY = 1;
public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
- public final static int RECENTS_GROW_TARGET_INVALID = -1;
public final static Set<String> RECENTS_ACTIVITIES = new HashSet<>();
static {
RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY);
}
- // Purely for experimentation
- private final static String RECENTS_OVERRIDE_SYSPROP_KEY = "persist.recents_override_pkg";
- private final static String ACTION_SHOW_RECENTS = "com.android.systemui.recents.ACTION_SHOW";
- private final static String ACTION_HIDE_RECENTS = "com.android.systemui.recents.ACTION_HIDE";
- private final static String ACTION_TOGGLE_RECENTS = "com.android.systemui.recents.ACTION_TOGGLE";
-
private static final String COUNTER_WINDOW_SUPPORTED = "window_enter_supported";
private static final String COUNTER_WINDOW_UNSUPPORTED = "window_enter_unsupported";
private static final String COUNTER_WINDOW_INCOMPATIBLE = "window_enter_incompatible";
@@ -107,11 +99,6 @@ public class Recents extends SystemUI
private static RecentsTaskLoader sTaskLoader;
private static RecentsConfiguration sConfiguration;
- // For experiments only, allows another package to handle recents if it is defined in the system
- // properties. This is limited to show/toggle/hide, and does not tie into the ActivityManager,
- // and does not reside in the home stack.
- private String mOverrideRecentsPackageName;
-
private Handler mHandler;
private RecentsImpl mImpl;
private int mDraggingInRecentsCurrentUser;
@@ -204,21 +191,23 @@ public class Recents extends SystemUI
@Override
public void start() {
- sDebugFlags = new RecentsDebugFlags(mContext);
+ final Resources res = mContext.getResources();
+ final int defaultTaskBarBackgroundColor =
+ mContext.getColor(R.color.recents_task_bar_default_background_color);
+ final int defaultTaskViewBackgroundColor =
+ mContext.getColor(R.color.recents_task_view_default_background_color);
+ sDebugFlags = new RecentsDebugFlags();
sSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
sConfiguration = new RecentsConfiguration(mContext);
- sTaskLoader = new RecentsTaskLoader(mContext);
+ sTaskLoader = new RecentsTaskLoader(mContext,
+ // TODO: Once we start building the AAR, move these into the loader
+ res.getInteger(R.integer.config_recents_max_thumbnail_count),
+ res.getInteger(R.integer.config_recents_max_icon_count),
+ res.getInteger(R.integer.recents_svelte_level));
+ sTaskLoader.setDefaultColors(defaultTaskBarBackgroundColor, defaultTaskViewBackgroundColor);
mHandler = new Handler();
mImpl = new RecentsImpl(mContext);
- // Check if there is a recents override package
- if (Build.IS_USERDEBUG || Build.IS_ENG) {
- String cnStr = SystemProperties.get(RECENTS_OVERRIDE_SYSPROP_KEY);
- if (!cnStr.isEmpty()) {
- mOverrideRecentsPackageName = cnStr;
- }
- }
-
// Register with the event bus
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
EventBus.getDefault().register(sSystemServicesProxy, EVENT_BUS_PRIORITY);
@@ -257,16 +246,8 @@ public class Recents extends SystemUI
return;
}
- if (proxyToOverridePackage(ACTION_SHOW_RECENTS)) {
- return;
- }
- try {
- ActivityManager.getService().closeSystemDialogs(SYSTEM_DIALOG_REASON_RECENT_APPS);
- } catch (RemoteException e) {
- }
-
+ sSystemServicesProxy.sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents();
-
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
@@ -301,10 +282,6 @@ public class Recents extends SystemUI
return;
}
- if (proxyToOverridePackage(ACTION_HIDE_RECENTS)) {
- return;
- }
-
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
@@ -336,12 +313,7 @@ public class Recents extends SystemUI
return;
}
- if (proxyToOverridePackage(ACTION_TOGGLE_RECENTS)) {
- return;
- }
-
int growTarget = getComponent(Divider.class).getView().growsRecents();
-
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.toggleRecents(growTarget);
@@ -634,6 +606,23 @@ public class Recents extends SystemUI
}
}
+ public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ int processUser = ssp.getProcessUser();
+ if (!ssp.isSystemUser(processUser)) {
+ postToSystemUser(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mUserToSystemCallbacks.sendDockedFirstAnimationFrameEvent();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ }
+ }
+ });
+ }
+ }
+
/**
* Handle screen pinning request.
*/
@@ -820,21 +809,6 @@ public class Recents extends SystemUI
(Settings.Secure.getInt(cr, Settings.Secure.USER_SETUP_COMPLETE, 0) != 0);
}
- /**
- * Attempts to proxy the following action to the override recents package.
- * @return whether the proxying was successful
- */
- private boolean proxyToOverridePackage(String action) {
- if (mOverrideRecentsPackageName != null) {
- Intent intent = new Intent(action);
- intent.setPackage(mOverrideRecentsPackageName);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendBroadcast(intent);
- return true;
- }
- return false;
- }
-
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Recents");
diff --git a/com/android/systemui/recents/RecentsActivity.java b/com/android/systemui/recents/RecentsActivity.java
index 86b77900..b75a1428 100644
--- a/com/android/systemui/recents/RecentsActivity.java
+++ b/com/android/systemui/recents/RecentsActivity.java
@@ -17,7 +17,6 @@
package com.android.systemui.recents;
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.TaskStackBuilder;
import android.app.WallpaperManager;
@@ -29,10 +28,10 @@ import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Secure;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
@@ -42,6 +41,7 @@ import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.LatencyTracker;
@@ -53,7 +53,6 @@ import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
-import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
@@ -61,10 +60,10 @@ import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationC
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
+import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.component.ActivityUnpinnedEvent;
@@ -79,28 +78,24 @@ import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
-import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsPackageMonitor;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.RecentsView;
import com.android.systemui.recents.views.SystemBarScrimViews;
import com.android.systemui.statusbar.phone.StatusBar;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.List;
/**
* The main Recents activity that is started from RecentsComponent.
@@ -114,7 +109,23 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
public final static int INCOMPATIBLE_APP_ALPHA_DURATION = 150;
- private RecentsPackageMonitor mPackageMonitor;
+ private PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
+ }
+
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
+ return true;
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
+ }
+ };
private Handler mHandler = new Handler();
private long mLastTabKeyEventTime;
private boolean mFinishedOnStartup;
@@ -133,7 +144,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
// The trigger to automatically launch the current task
private int mFocusTimerDuration;
- private DozeTrigger mIterateTrigger;
private final UserInteractionEvent mUserInteractionEvent = new UserInteractionEvent();
// Theme and colors
@@ -188,41 +198,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
} else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
// When switching users, dismiss Recents to Home similar to screen off
finish();
- } else if (action.equals(Intent.ACTION_TIME_CHANGED)) {
- // If the time shifts but the currentTime >= lastStackActiveTime, then that boundary
- // is still valid. Otherwise, we need to reset the lastStackactiveTime to the
- // currentTime and remove the old tasks in between which would not be previously
- // visible, but currently would be in the new currentTime
- int currentUser = SystemServicesProxy.getInstance(RecentsActivity.this)
- .getCurrentUser();
- long oldLastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(),
- Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1, currentUser);
- if (oldLastStackActiveTime != -1) {
- long currentTime = System.currentTimeMillis();
- if (currentTime < oldLastStackActiveTime) {
- // We are only removing tasks that are between the new current time
- // and the old last stack active time, they were not visible and in the
- // TaskStack so we don't need to remove any associated TaskViews but we do
- // need to load the task id's from the system
- RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(ctx);
- loader.preloadRawTasks(loadPlan, false /* includeFrontMostExcludedTask */);
- List<ActivityManager.RecentTaskInfo> tasks = loadPlan.getRawTasks();
- for (int i = tasks.size() - 1; i >= 0; i--) {
- ActivityManager.RecentTaskInfo task = tasks.get(i);
- if (currentTime <= task.lastActiveTime && task.lastActiveTime <
- oldLastStackActiveTime) {
- Recents.getSystemServices().removeTask(task.persistentId);
- }
- }
- Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
- currentTime, currentUser);
-
- // Clear the last PiP task time, it's an edge case and we'd rather it
- // not relaunch the PiP task if the user double taps
- RecentsImpl.clearLastPipTime();
- }
- }
}
}
};
@@ -340,8 +315,8 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
// Initialize the package monitor
- mPackageMonitor = new RecentsPackageMonitor();
- mPackageMonitor.register(this);
+ mPackageMonitor.register(this, Looper.getMainLooper(), UserHandle.ALL,
+ true /* externalStorage */);
// Select theme based on wallpaper colors
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
@@ -363,13 +338,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
}
mLastConfig = new Configuration(Utilities.getAppConfiguration(this));
- mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration);
- mIterateTrigger = new DozeTrigger(mFocusTimerDuration, new Runnable() {
- @Override
- public void run() {
- dismissRecentsToFocusedTask(MetricsEvent.OVERVIEW_SELECT_TIMEOUT);
- }
- });
// Set the window background
mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode());
@@ -383,7 +351,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
// Register the broadcast receiver to handle messages when the screen is turned off
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
registerReceiver(mSystemBroadcastReceiver, filter);
@@ -460,22 +427,21 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan loadPlan = RecentsImpl.consumeInstanceLoadPlan();
if (loadPlan == null) {
- loadPlan = loader.createLoadPlan(this);
+ loadPlan = new RecentsTaskLoadPlan(this);
}
// Start loading tasks according to the load plan
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!loadPlan.hasTasks()) {
- loader.preloadTasks(loadPlan, launchState.launchedToTaskId,
- !launchState.launchedFromHome && !launchState.launchedViaDockGesture);
+ loader.preloadTasks(loadPlan, launchState.launchedToTaskId);
}
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
loadOpts.runningTaskId = launchState.launchedToTaskId;
loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
- loader.loadTasks(this, loadPlan, loadOpts);
+ loader.loadTasks(loadPlan, loadOpts);
TaskStack stack = loadPlan.getTaskStack();
mRecentsView.onReload(stack, mIsVisible);
@@ -540,7 +506,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
super.onPause();
mIgnoreAltTabRelease = false;
- mIterateTrigger.stopDozing();
}
@Override
@@ -643,8 +608,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
if (backward) {
EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
} else {
- EventBus.getDefault().send(
- new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */));
+ EventBus.getDefault().send(new FocusNextTaskViewEvent());
}
mLastTabKeyEventTime = SystemClock.elapsedRealtime();
@@ -702,38 +666,10 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
}
}
- public final void onBusEvent(IterateRecentsEvent event) {
- final RecentsDebugFlags debugFlags = Recents.getDebugFlags();
-
- // Start dozing after the recents button is clicked
- int timerIndicatorDuration = 0;
- if (debugFlags.isFastToggleRecentsEnabled()) {
- timerIndicatorDuration = getResources().getInteger(
- R.integer.recents_subsequent_auto_advance_duration);
-
- mIterateTrigger.setDozeDuration(timerIndicatorDuration);
- if (!mIterateTrigger.isDozing()) {
- mIterateTrigger.startDozing();
- } else {
- mIterateTrigger.poke();
- }
- }
-
- // Focus the next task
- EventBus.getDefault().send(new FocusNextTaskViewEvent(timerIndicatorDuration));
-
- MetricsLogger.action(this, MetricsEvent.ACTION_OVERVIEW_PAGE);
- }
-
public final void onBusEvent(RecentsActivityStartingEvent event) {
mRecentsStartRequested = true;
}
- public final void onBusEvent(UserInteractionEvent event) {
- // Stop the fast-toggle dozer
- mIterateTrigger.stopDozing();
- }
-
public final void onBusEvent(HideRecentsEvent event) {
if (event.triggeredFromAltTab) {
// If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
@@ -751,15 +687,11 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
}
public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
- EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true));
mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
mRecentsView.invalidate();
}
public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
- if (mRecentsView.isLastTaskLaunchedFreeform()) {
- EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(false));
- }
mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
mRecentsView.invalidate();
}
@@ -859,11 +791,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
MetricsLogger.count(this, "overview_screen_pinned", 1);
}
- public final void onBusEvent(DebugFlagsChangedEvent event) {
- // Just finish recents so that we can reload the flags anew on the next instantiation
- finish();
- }
-
public final void onBusEvent(StackViewScrolledEvent event) {
// Once the user has scrolled while holding alt-tab, then we should ignore the release of
// the key
@@ -888,14 +815,13 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
- loader.preloadTasks(loadPlan, -1 /* runningTaskId */,
- false /* includeFrontMostExcludedTask */);
+ RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(this);
+ loader.preloadTasks(loadPlan, -1 /* runningTaskId */);
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
- loader.loadTasks(this, loadPlan, loadOpts);
+ loader.loadTasks(loadPlan, loadOpts);
TaskStack stack = loadPlan.getTaskStack();
int numStackTasks = stack.getStackTaskCount();
@@ -924,6 +850,11 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
return true;
}
+ public void onPackageChanged(String packageName, int userId) {
+ Recents.getTaskLoader().onPackageChanged(packageName);
+ EventBus.getDefault().send(new PackagesChangedEvent(packageName, userId));
+ }
+
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
@@ -931,13 +862,9 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
Recents.getTaskLoader().dump(prefix, writer);
String id = Integer.toHexString(System.identityHashCode(this));
- long lastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(),
- Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1,
- SystemServicesProxy.getInstance(this).getCurrentUser());
writer.print(prefix); writer.print(TAG);
writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N");
- writer.print(" lastStackTaskActiveTime="); writer.print(lastStackActiveTime);
writer.print(" currentTime="); writer.print(System.currentTimeMillis());
writer.print(" [0x"); writer.print(id); writer.print("]");
writer.println();
diff --git a/com/android/systemui/recents/RecentsActivityLaunchState.java b/com/android/systemui/recents/RecentsActivityLaunchState.java
index 5b8ed94d..d2326ce2 100644
--- a/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -33,7 +33,6 @@ public class RecentsActivityLaunchState {
public boolean launchedFromPipApp;
// Set if the next activity that quick-switch will launch is the PiP activity
public boolean launchedWithNextPipApp;
- public boolean launchedFromBlacklistedApp;
public boolean launchedFromHome;
public boolean launchedViaDragGesture;
public boolean launchedViaDockGesture;
@@ -44,7 +43,6 @@ public class RecentsActivityLaunchState {
public void reset() {
launchedFromHome = false;
launchedFromApp = false;
- launchedFromBlacklistedApp = false;
launchedFromPipApp = false;
launchedWithNextPipApp = false;
launchedToTaskId = -1;
@@ -60,18 +58,6 @@ public class RecentsActivityLaunchState {
RecentsDebugFlags debugFlags = Recents.getDebugFlags();
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (launchedFromApp) {
- if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
- // If fast toggling, focus the front most task so that the next tap will launch the
- // task
- return numTasks - 1;
- }
-
- if (launchState.launchedFromBlacklistedApp) {
- // If we are launching from a blacklisted app, focus the front most task so that the
- // next tap will launch the task
- return numTasks - 1;
- }
-
if (useGridLayout) {
// If coming from another app to the grid layout, focus the front most task
return numTasks - 1;
@@ -80,12 +66,6 @@ public class RecentsActivityLaunchState {
// If coming from another app, focus the next task
return Math.max(0, numTasks - 2);
} else {
- if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
- // If fast toggling, defer focusing until the next tap (which will automatically
- // focus the front most task)
- return -1;
- }
-
// If coming from home, focus the front most task
return numTasks - 1;
}
diff --git a/com/android/systemui/recents/RecentsConfiguration.java b/com/android/systemui/recents/RecentsConfiguration.java
index 5dc6f31c..68df1d5b 100644
--- a/com/android/systemui/recents/RecentsConfiguration.java
+++ b/com/android/systemui/recents/RecentsConfiguration.java
@@ -20,31 +20,31 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Rect;
import android.os.SystemProperties;
import com.android.systemui.R;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.DockState;
+import com.android.systemui.shared.recents.model.TaskStack;
/**
* Represents the dock regions for each orientation.
*/
class DockRegion {
- public static TaskStack.DockState[] PHONE_LANDSCAPE = {
+ public static DockState[] PHONE_LANDSCAPE = {
// We only allow docking to the left in landscape for now on small devices
- TaskStack.DockState.LEFT
+ DockState.LEFT
};
- public static TaskStack.DockState[] PHONE_PORTRAIT = {
+ public static DockState[] PHONE_PORTRAIT = {
// We only allow docking to the top for now on small devices
- TaskStack.DockState.TOP
+ DockState.TOP
};
- public static TaskStack.DockState[] TABLET_LANDSCAPE = {
- TaskStack.DockState.LEFT,
- TaskStack.DockState.RIGHT
+ public static DockState[] TABLET_LANDSCAPE = {
+ DockState.LEFT,
+ DockState.RIGHT
};
- public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
+ public static DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
}
/**
@@ -56,18 +56,6 @@ public class RecentsConfiguration {
private static final int LARGE_SCREEN_MIN_DP = 600;
private static final int XLARGE_SCREEN_MIN_DP = 720;
- /** Levels of svelte in increasing severity/austerity. */
- // No svelting.
- public static final int SVELTE_NONE = 0;
- // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable
- // caching thumbnails as you scroll.
- public static final int SVELTE_LIMIT_CACHE = 1;
- // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and
- // evict all thumbnails when hidden.
- public static final int SVELTE_DISABLE_CACHE = 2;
- // Disable all thumbnail loading.
- public static final int SVELTE_DISABLE_LOADING = 3;
-
// Launch states
public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState();
@@ -125,7 +113,7 @@ public class RecentsConfiguration {
* Returns the preferred dock states for the current orientation.
* @return a list of dock states for device and its orientation
*/
- public TaskStack.DockState[] getDockStatesForCurrentOrientation() {
+ public DockState[] getDockStatesForCurrentOrientation() {
boolean isLandscape = mAppContext.getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
RecentsConfiguration config = Recents.getConfiguration();
diff --git a/com/android/systemui/recents/RecentsDebugFlags.java b/com/android/systemui/recents/RecentsDebugFlags.java
index 0262a098..19185939 100644
--- a/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/com/android/systemui/recents/RecentsDebugFlags.java
@@ -16,75 +16,14 @@
package com.android.systemui.recents;
-import android.content.Context;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.tuner.TunerService;
-
-/**
- * Tunable debug flags
- */
-public class RecentsDebugFlags implements TunerService.Tunable {
+public class RecentsDebugFlags {
public static class Static {
// Enables debug drawing for the transition thumbnail
public static final boolean EnableTransitionThumbnailDebugMode = false;
- // This disables the bitmap and icon caches
- public static final boolean DisableBackgroundCache = false;
- // Enables the task affiliations
- public static final boolean EnableAffiliatedTaskGroups = false;
- // Enables the button above the stack
- public static final boolean EnableStackActionButton = true;
- // Overrides the Tuner flags and enables the timeout
- private static final boolean EnableFastToggleTimeout = false;
- // Overrides the Tuner flags and enables the paging via the Recents button
- private static final boolean EnablePaging = false;
+
// Disables enter and exit transitions for other tasks for low ram devices
public static final boolean DisableRecentsLowRamEnterExitAnimation = false;
- // Enables us to create mock recents tasks
- public static final boolean EnableMockTasks = false;
- // Defines the number of mock recents packages to create
- public static final int MockTasksPackageCount = 3;
- // Defines the number of mock recents tasks to create
- public static final int MockTaskCount = 100;
- // Enables the simulated task affiliations
- public static final boolean EnableMockTaskGroups = false;
- // Defines the number of mock task affiliations per group
- public static final int MockTaskGroupsTaskCount = 12;
- }
-
- /**
- * We read the prefs once when we start the activity, then update them as the tuner changes
- * the flags.
- */
- public RecentsDebugFlags(Context context) {
- // Register all our flags, this will also call onTuningChanged() for each key, which will
- // initialize the current state of each flag
- }
-
- /**
- * @return whether we are enabling fast toggling.
- */
- public boolean isFastToggleRecentsEnabled() {
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.hasFreeformWorkspaceSupport() || ssp.isTouchExplorationEnabled()) {
- return false;
- }
- return Static.EnableFastToggleTimeout;
- }
-
- /**
- * @return whether we are enabling paging.
- */
- public boolean isPagingEnabled() {
- return Static.EnablePaging;
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- EventBus.getDefault().send(new DebugFlagsChangedEvent());
}
}
diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java
index 3e2a5f3f..868ed64b 100644
--- a/com/android/systemui/recents/RecentsImpl.java
+++ b/com/android/systemui/recents/RecentsImpl.java
@@ -18,7 +18,6 @@ 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.WINDOWING_MODE_FREEFORM;
import static android.view.View.MeasureSpec;
import android.app.ActivityManager;
@@ -56,7 +55,6 @@ import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
@@ -72,20 +70,18 @@ import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.ForegroundThread;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.Task.TaskKey;
-import com.android.systemui.recents.model.TaskGrouping;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.recents.views.RecentsTransitionHelper;
import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.views.TaskStackViewScroller;
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
@@ -117,10 +113,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
/**
- * An implementation of TaskStackListener, that allows us to listen for changes to the system
+ * An implementation of TaskStackChangeListener, that allows us to listen for changes to the system
* task stacks and update recents accordingly.
*/
- class TaskStackListenerImpl extends TaskStackListener {
+ class TaskStackListenerImpl extends TaskStackChangeListener {
@Override
public void onTaskStackChangedBackground() {
@@ -131,7 +127,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Preloads the next task
RecentsConfiguration config = Recents.getConfiguration();
- if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
+ if (config.svelteLevel == RecentsTaskLoader.SVELTE_NONE) {
Rect windowRect = getWindowRect(null /* windowRectOverride */);
if (windowRect.isEmpty()) {
return;
@@ -141,8 +137,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+ loader.preloadTasks(plan, -1);
TaskStack stack = plan.getTaskStack();
RecentsActivityLaunchState launchState = new RecentsActivityLaunchState();
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
@@ -168,7 +164,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
launchOpts.onlyLoadPausedActivities = true;
launchOpts.loadThumbnails = true;
}
- loader.loadTasks(mContext, plan, launchOpts);
+ loader.loadTasks(plan, launchOpts);
}
}
@@ -207,7 +203,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
}
EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId,
- ThumbnailData.createFromTaskSnapshot(snapshot)));
+ new ThumbnailData(snapshot)));
}
}
@@ -282,13 +278,13 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// When we start, preload the data associated with the previous recent tasks.
// We can use a new plan since the caches will be the same.
RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+ loader.preloadTasks(plan, -1);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
launchOpts.numVisibleTasks = loader.getIconCacheSize();
launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
launchOpts.onlyLoadForCache = true;
- loader.loadTasks(mContext, plan, launchOpts);
+ loader.loadTasks(plan, launchOpts);
}
public void onConfigurationChanged() {
@@ -409,22 +405,17 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!launchState.launchedWithAltTab) {
- // Has the user tapped quickly?
- boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
if (Recents.getConfiguration().isGridEnabled) {
+ // Has the user tapped quickly?
+ boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
if (isQuickTap) {
EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
} else {
EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent());
}
} else {
- if (!debugFlags.isPagingEnabled() || isQuickTap) {
- // Launch the next focused task
- EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
- } else {
- // Notify recents to move onto the next task
- EventBus.getDefault().post(new IterateRecentsEvent());
- }
+ // Launch the next focused task
+ EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
}
} else {
// If the user has toggled it too quickly, then just eat up the event here (it's
@@ -473,16 +464,15 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// RecentsActivity) only if there is a task to animate to. Post this to ensure that we
// don't block the touch feedback on the nav bar button which triggers this.
mHandler.post(() -> {
- MutableBoolean isHomeStackVisible = new MutableBoolean(true);
- if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
+ if (!ssp.isRecentsActivityVisible(null)) {
ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
if (runningTask == null) {
return;
}
RecentsTaskLoader loader = Recents.getTaskLoader();
- sInstanceLoadPlan = loader.createLoadPlan(mContext);
- loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
+ sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
+ loader.preloadTasks(sInstanceLoadPlan, runningTask.id);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
if (stack.getTaskCount() > 0) {
// Only preload the icon (but not the thumbnail since it may not have been taken
@@ -521,8 +511,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
public void showNextTask() {
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+ loader.preloadTasks(plan, -1);
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
@@ -576,8 +566,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
public void showRelativeAffiliatedTask(boolean showNextTask) {
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+ loader.preloadTasks(plan, -1);
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
@@ -595,43 +585,38 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
Task toTask = null;
ActivityOptions launchOpts = null;
int taskCount = tasks.size();
- int numAffiliatedTasks = 0;
for (int i = 0; i < taskCount; i++) {
Task task = tasks.get(i);
if (task.key.id == runningTask.id) {
- TaskGrouping group = task.group;
- Task.TaskKey toTaskKey;
if (showNextTask) {
- toTaskKey = group.getNextTaskInGroup(task);
- launchOpts = ActivityOptions.makeCustomAnimation(mContext,
- R.anim.recents_launch_next_affiliated_task_target,
- R.anim.recents_launch_next_affiliated_task_source);
+ if ((i + 1) < taskCount) {
+ toTask = tasks.get(i + 1);
+ launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.recents_launch_next_affiliated_task_target,
+ R.anim.recents_launch_next_affiliated_task_source);
+ }
} else {
- toTaskKey = group.getPrevTaskInGroup(task);
- launchOpts = ActivityOptions.makeCustomAnimation(mContext,
- R.anim.recents_launch_prev_affiliated_task_target,
- R.anim.recents_launch_prev_affiliated_task_source);
- }
- if (toTaskKey != null) {
- toTask = focusedStack.findTaskWithId(toTaskKey.id);
+ if ((i - 1) >= 0) {
+ toTask = tasks.get(i - 1);
+ launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.recents_launch_prev_affiliated_task_target,
+ R.anim.recents_launch_prev_affiliated_task_source);
+ }
}
- numAffiliatedTasks = group.getTaskCount();
break;
}
}
// Return early if there is no next task
if (toTask == null) {
- if (numAffiliatedTasks > 1) {
- if (showNextTask) {
- ssp.startInPlaceAnimationOnFrontMostApplication(
- ActivityOptions.makeCustomInPlaceAnimation(mContext,
- R.anim.recents_launch_next_affiliated_task_bounce));
- } else {
- ssp.startInPlaceAnimationOnFrontMostApplication(
- ActivityOptions.makeCustomInPlaceAnimation(mContext,
- R.anim.recents_launch_prev_affiliated_task_bounce));
- }
+ if (showNextTask) {
+ ssp.startInPlaceAnimationOnFrontMostApplication(
+ ActivityOptions.makeCustomInPlaceAnimation(mContext,
+ R.anim.recents_launch_next_affiliated_task_bounce));
+ } else {
+ ssp.startInPlaceAnimationOnFrontMostApplication(
+ ActivityOptions.makeCustomInPlaceAnimation(mContext,
+ R.anim.recents_launch_prev_affiliated_task_bounce));
}
return;
}
@@ -753,8 +738,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
systemInsets.left, systemInsets.right, mTmpBounds);
stackLayout.reset();
- stackLayout.initialize(displayRect, windowRect, mTmpBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
+ stackLayout.initialize(displayRect, windowRect, mTmpBounds);
}
}
@@ -843,7 +827,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
launchOpts.runningTaskId = runningTaskId;
launchOpts.loadThumbnails = false;
launchOpts.onlyLoadForCache = true;
- Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
+ Recents.getTaskLoader().loadTasks(sInstanceLoadPlan, launchOpts);
}
/**
@@ -873,61 +857,29 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask,
Rect windowOverrideRect) {
final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
- 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();
- TaskStackViewScroller stackScroller = mDummyStackView.getScroller();
-
- mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */);
- mDummyStackView.updateToInitialState();
-
- for (int i = tasks.size() - 1; i >= 0; i--) {
- Task task = tasks.get(i);
- if (task.isFreeformTask()) {
- mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
- stackScroller.getStackScroll(), mTmpTransform, null,
- windowOverrideRect);
- GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform);
- Rect toTaskRect = new Rect();
- mTmpTransform.rect.round(toTaskRect);
- specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
- }
- }
- AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
- specs.toArray(specsArray);
-
- // For low end ram devices, wait for transition flag is reset when Recents entrance
- // animation is complete instead of when the transition animation starts
- return new Pair<>(ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
- specsArray, mHandler, isLowRamDevice ? null : mResetToggleFlagListener, this),
- null);
- } else {
- // Update the destination rect
- Task toTask = new Task();
- TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
- windowOverrideRect);
-
- RectF toTaskRect = toTransform.rect;
- AppTransitionAnimationSpecsFuture future =
- new RecentsTransitionHelper(mContext).getAppTransitionFuture(
- () -> {
- Rect rect = new Rect();
- toTaskRect.round(rect);
- GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
- toTransform);
- return Lists.newArrayList(new AppTransitionAnimationSpec(
- toTask.key.id, thumbnail, rect));
- });
-
- // For low end ram devices, wait for transition flag is reset when Recents entrance
- // animation is complete instead of when the transition animation starts
- return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
- mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
- false /* scaleUp */), future);
- }
+
+ // Update the destination rect
+ Task toTask = new Task();
+ TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
+ windowOverrideRect);
+
+ RectF toTaskRect = toTransform.rect;
+ AppTransitionAnimationSpecsFuture future =
+ new RecentsTransitionHelper(mContext).getAppTransitionFuture(
+ () -> {
+ Rect rect = new Rect();
+ toTaskRect.round(rect);
+ GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
+ toTransform);
+ return Lists.newArrayList(new AppTransitionAnimationSpec(
+ toTask.key.id, thumbnail, rect));
+ });
+
+ // For low end ram devices, wait for transition flag is reset when Recents entrance
+ // animation is complete instead of when the transition animation starts
+ return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
+ mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
+ false /* scaleUp */), future);
}
/**
@@ -942,7 +894,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
runningTaskOut.copyFrom(launchTask);
} else {
// If no task is specified or we can not find the task just use the front most one
- launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
+ launchTask = stack.getStackFrontMostTask();
runningTaskOut.copyFrom(launchTask);
}
@@ -995,12 +947,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
boolean isHomeStackVisible, boolean animate, int growTarget) {
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
- SystemServicesProxy ssp = Recents.getSystemServices();
- boolean isBlacklisted = (runningTask != null)
- ? ssp.isBlackListedActivity(runningTask.baseActivity.getClassName())
- : false;
- int runningTaskId = !mLaunchedWhileDocking && !isBlacklisted && (runningTask != null)
+ int runningTaskId = !mLaunchedWhileDocking && (runningTask != null)
? runningTask.id
: -1;
@@ -1009,10 +957,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// the stacks might have changed.
if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
// Create a new load plan if preloadRecents() was never triggered
- sInstanceLoadPlan = loader.createLoadPlan(mContext);
+ sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
}
if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
- loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible);
+ loader.preloadTasks(sInstanceLoadPlan, runningTaskId);
}
TaskStack stack = sInstanceLoadPlan.getTaskStack();
@@ -1023,7 +971,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Update the launch state that we need in updateHeaderBarLayout()
launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
- launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted;
launchState.launchedFromPipApp = false;
launchState.launchedWithNextPipApp =
stack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime());
@@ -1059,9 +1006,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
}
Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair;
- if (isBlacklisted) {
- pair = new Pair<>(getUnknownTransitionActivityOptions(), null);
- } else if (useThumbnailTransition) {
+ if (useThumbnailTransition) {
// Try starting with a thumbnail transition
pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect);
} else {
diff --git a/com/android/systemui/recents/RecentsSystemUser.java b/com/android/systemui/recents/RecentsSystemUser.java
index 12856260..ff1f7dc5 100644
--- a/com/android/systemui/recents/RecentsSystemUser.java
+++ b/com/android/systemui/recents/RecentsSystemUser.java
@@ -27,6 +27,7 @@ import android.util.SparseArray;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
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.component.SetWaitingForTransitionStartEvent;
@@ -108,6 +109,11 @@ public class RecentsSystemUser extends IRecentsSystemUserCallbacks.Stub {
}
@Override
+ public void sendDockedFirstAnimationFrameEvent() throws RemoteException {
+ EventBus.getDefault().post(new DockedFirstAnimationFrameEvent());
+ }
+
+ @Override
public void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart) {
EventBus.getDefault().post(new SetWaitingForTransitionStartEvent(
waitingForTransitionStart));
diff --git a/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java b/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
index 7604de1d..fec34e3c 100644
--- a/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
+++ b/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.activity;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
/**
* This is sent when we want to cancel the enter-recents window animation for the launch task.
diff --git a/com/android/systemui/recents/events/activity/IterateRecentsEvent.java b/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
deleted file mode 100644
index f7b2706b..00000000
--- a/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
+++ /dev/null
@@ -1,27 +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.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the user taps on the Overview button to iterate to the next item in the
- * Recents list.
- */
-public class IterateRecentsEvent extends EventBus.Event {
- // Simple event
-}
diff --git a/com/android/systemui/recents/events/activity/LaunchTaskEvent.java b/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
index 862a1eee..2409f39d 100644
--- a/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
+++ b/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
@@ -22,7 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.graphics.Rect;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.TaskView;
/**
diff --git a/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java b/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
index 64eeafa1..e4972b1f 100644
--- a/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
+++ b/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.activity;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.TaskStack;
/**
* This is sent by the activity whenever the multi-window state has changed.
diff --git a/com/android/systemui/recents/events/activity/PackagesChangedEvent.java b/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
index 3b68574c..47670e03 100644
--- a/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
+++ b/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
@@ -17,22 +17,20 @@
package com.android.systemui.recents.events.activity;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.RecentsPackageMonitor;
import com.android.systemui.recents.views.TaskStackView;
+import com.android.systemui.recents.RecentsActivity;
/**
- * This event is sent by {@link RecentsPackageMonitor} when a package on the the system changes.
+ * This event is sent by {@link RecentsActivity} when a package on the the system changes.
* {@link TaskStackView}s listen for this event, and remove the tasks associated with the removed
* packages.
*/
public class PackagesChangedEvent extends EventBus.Event {
- public final RecentsPackageMonitor monitor;
public final String packageName;
public final int userId;
- public PackagesChangedEvent(RecentsPackageMonitor monitor, String packageName, int userId) {
- this.monitor = monitor;
+ public PackagesChangedEvent(String packageName, int userId) {
this.packageName = packageName;
this.userId = userId;
}
diff --git a/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java b/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
index 0d614e8c..51d02b5b 100644
--- a/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
+++ b/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.activity;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.TaskStack;
/**
* This is sent by the activity whenever the task stach has changed.
diff --git a/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java b/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
index 4ed02708..b52e83b8 100644
--- a/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
+++ b/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.ui;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
/**
* This is sent when the data associated with a given {@link Task} should be deleted from the
diff --git a/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java b/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
index 40c30b88..da19384a 100644
--- a/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
+++ b/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.ui;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
/**
* This is sent when a user wants to show the application info for a {@link Task}.
diff --git a/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java b/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
index e0ed7a9e..f0829280 100644
--- a/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
+++ b/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.ui;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.model.ThumbnailData;
/**
* Sent when a task snapshot has changed.
diff --git a/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java b/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
index 0628c501..881a64af 100644
--- a/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
+++ b/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
@@ -17,8 +17,8 @@
package com.android.systemui.recents.events.ui;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.AnimationProps;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
import com.android.systemui.recents.views.TaskView;
/**
diff --git a/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java b/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
deleted file mode 100644
index b42da9c7..00000000
--- a/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
+++ /dev/null
@@ -1,31 +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.systemui.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent to update the visibility of all visible freeform task views.
- */
-public class UpdateFreeformTaskViewVisibilityEvent extends EventBus.Event {
-
- public final boolean visible;
-
- public UpdateFreeformTaskViewVisibilityEvent(boolean visible) {
- this.visible = visible;
- }
-}
diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
index 216be612..cf61b1ef 100644
--- a/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
+++ b/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.ui.dragndrop;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.DropTarget;
/**
diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
index edd79959..297afc53 100644
--- a/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
+++ b/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
@@ -17,9 +17,8 @@
package com.android.systemui.recents.events.ui.dragndrop;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.DropTarget;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.TaskView;
/**
diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
index 73c282fe..73cbde99 100644
--- a/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
+++ b/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.ui.dragndrop;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.DropTarget;
import com.android.systemui.recents.views.TaskView;
diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
index e57fa2d8..021be77b 100644
--- a/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
+++ b/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
@@ -19,7 +19,7 @@ package com.android.systemui.recents.events.ui.dragndrop;
import android.graphics.Point;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.TaskView;
/**
diff --git a/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java b/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
index 7030729d..64ba5748 100644
--- a/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
+++ b/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
@@ -17,7 +17,7 @@
package com.android.systemui.recents.events.ui.dragndrop;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.RecentsViewTouchHandler;
import com.android.systemui.recents.views.TaskView;
diff --git a/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
index a1e4957a..171ab5e8 100644
--- a/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
+++ b/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
@@ -22,10 +22,5 @@ import com.android.systemui.recents.events.EventBus;
* Focuses the next task view in the stack.
*/
public class FocusNextTaskViewEvent extends EventBus.Event {
-
- public final int timerIndicatorDuration;
-
- public FocusNextTaskViewEvent(int timerIndicatorDuration) {
- this.timerIndicatorDuration = timerIndicatorDuration;
- }
+ // Simple event
}
diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java
index bddf9a59..87f24fdb 100644
--- a/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -25,27 +25,20 @@ 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;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.StackInfo;
-import android.app.ActivityManager.TaskSnapshot;
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;
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.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -55,24 +48,18 @@ import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.IRemoteCallback;
-import android.os.Message;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
-import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.provider.Settings.Secure;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
-import android.util.ArraySet;
-import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.MutableBoolean;
import android.view.Display;
@@ -89,19 +76,12 @@ import com.android.internal.os.BackgroundThread;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.UiOffloadThread;
-import com.android.systemui.pip.tv.PipMenuActivity;
import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.RecentsImpl;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.statusbar.policy.UserInfoController;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
-import java.util.Random;
/**
* Acts as a shim around the real system services that we need to access data from, and provides
@@ -117,42 +97,32 @@ public class SystemServicesProxy {
sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
}
- final static List<String> sRecentsBlacklist;
- static {
- sRecentsBlacklist = new ArrayList<>();
- sRecentsBlacklist.add(PipMenuActivity.class.getName());
- }
-
private static SystemServicesProxy sSystemServicesProxy;
AccessibilityManager mAccm;
ActivityManager mAm;
IActivityManager mIam;
PackageManager mPm;
- IconDrawableFactory mDrawableFactory;
IPackageManager mIpm;
private final IDreamManager mDreamManager;
private final Context mContext;
AssistUtils mAssistUtils;
WindowManager mWm;
IWindowManager mIwm;
- KeyguardManager mKgm;
UserManager mUm;
Display mDisplay;
String mRecentsPackage;
- ComponentName mAssistComponent;
+ private TaskStackChangeListeners mTaskStackChangeListeners;
private int mCurrentUserId;
boolean mIsSafeMode;
- boolean mHasFreeformWorkspaceSupport;
- Bitmap mDummyIcon;
int mDummyThumbnailWidth;
int mDummyThumbnailHeight;
Paint mBgProtectionPaint;
Canvas mBgProtectionCanvas;
- private final Handler mHandler = new H();
+ private final Handler mHandler = new Handler();
private final Runnable mGcRunnable = new Runnable() {
@Override
public void run() {
@@ -163,144 +133,10 @@ public class SystemServicesProxy {
private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
- /**
- * An abstract class to track task stack changes.
- * Classes should implement this instead of {@link android.app.ITaskStackListener}
- * to reduce IPC calls from system services. These callbacks will be called on the main thread.
- */
- public abstract static class TaskStackListener {
- /**
- * NOTE: This call is made of the thread that the binder call comes in on.
- */
- public void onTaskStackChangedBackground() { }
- public void onTaskStackChanged() { }
- public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
- public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
- public void onActivityUnpinned() { }
- public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
- public void onPinnedStackAnimationStarted() { }
- public void onPinnedStackAnimationEnded() { }
- public void onActivityForcedResizable(String packageName, int taskId, int reason) { }
- public void onActivityDismissingDockedStack() { }
- public void onActivityLaunchOnSecondaryDisplayFailed() { }
- public void onTaskProfileLocked(int taskId, int userId) { }
-
- /**
- * Checks that the current user matches the user's SystemUI process. Since
- * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
- * TaskStackListener should make this call to verify that we don't act on events from other
- * user's processes.
- */
- protected final boolean checkCurrentUserId(Context context, boolean debug) {
- int processUserId = UserHandle.myUserId();
- int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
- if (processUserId != currentUserId) {
- if (debug) {
- Log.d(TAG, "UID mismatch. SystemUI is running uid=" + processUserId
- + " and the current user is uid=" + currentUserId);
- }
- return false;
- }
- return true;
- }
- }
-
- /**
- * Implementation of {@link android.app.ITaskStackListener} to listen task stack changes from
- * ActivityManagerService.
- * This simply passes callbacks to listeners through {@link H}.
- * */
- private android.app.TaskStackListener mTaskStackListener = new android.app.TaskStackListener() {
-
- private final List<SystemServicesProxy.TaskStackListener> mTmpListeners = new ArrayList<>();
-
- @Override
- public void onTaskStackChanged() throws RemoteException {
- // Call the task changed callback for the non-ui thread listeners first
- synchronized (mTaskStackListeners) {
- mTmpListeners.clear();
- mTmpListeners.addAll(mTaskStackListeners);
- }
- for (int i = mTmpListeners.size() - 1; i >= 0; i--) {
- mTmpListeners.get(i).onTaskStackChangedBackground();
- }
-
- mHandler.removeMessages(H.ON_TASK_STACK_CHANGED);
- mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED);
- }
-
- @Override
- 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,
- new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget();
- }
-
- @Override
- public void onActivityUnpinned() throws RemoteException {
- mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
- mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
- }
-
- @Override
- public void onPinnedActivityRestartAttempt(boolean clearedTask)
- throws RemoteException{
- mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
- mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, clearedTask ? 1 : 0, 0)
- .sendToTarget();
- }
-
- @Override
- public void onPinnedStackAnimationStarted() throws RemoteException {
- mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED);
- mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED);
- }
-
- @Override
- public void onPinnedStackAnimationEnded() throws RemoteException {
- mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
- mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
- }
-
- @Override
- public void onActivityForcedResizable(String packageName, int taskId, int reason)
- throws RemoteException {
- mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
- .sendToTarget();
- }
-
- @Override
- public void onActivityDismissingDockedStack() throws RemoteException {
- mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK);
- }
-
- @Override
- public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException {
- mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED);
- }
-
- @Override
- public void onTaskProfileLocked(int taskId, int userId) {
- mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
- }
-
- @Override
- public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
- throws RemoteException {
- mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
- }
- };
-
private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
(String name, Drawable picture, String userAccount) ->
mCurrentUserId = mAm.getCurrentUser();
- /**
- * List of {@link TaskStackListener} registered from {@link #registerTaskStackListener}.
- */
- private List<TaskStackListener> mTaskStackListeners = new ArrayList<>();
-
/** Private constructor */
private SystemServicesProxy(Context context) {
mContext = context.getApplicationContext();
@@ -308,23 +144,18 @@ public class SystemServicesProxy {
mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
mIam = ActivityManager.getService();
mPm = context.getPackageManager();
- mDrawableFactory = IconDrawableFactory.newInstance(context);
mIpm = AppGlobals.getPackageManager();
mAssistUtils = new AssistUtils(context);
mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mIwm = WindowManagerGlobal.getWindowManagerService();
- mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
mUm = UserManager.get(context);
mDreamManager = IDreamManager.Stub.asInterface(
ServiceManager.checkService(DreamService.DREAM_SERVICE));
mDisplay = mWm.getDefaultDisplay();
mRecentsPackage = context.getPackageName();
- mHasFreeformWorkspaceSupport =
- mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT) ||
- Settings.Global.getInt(context.getContentResolver(),
- DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
mIsSafeMode = mPm.isSafeMode();
mCurrentUserId = mAm.getCurrentUser();
+ mTaskStackChangeListeners = new TaskStackChangeListeners(Looper.getMainLooper());
// Get the dummy thumbnail width/heights
Resources res = context.getResources();
@@ -339,23 +170,11 @@ public class SystemServicesProxy {
mBgProtectionPaint.setColor(0xFFffffff);
mBgProtectionCanvas = new Canvas();
- // Resolve the assist intent
- mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId());
-
// Since SystemServicesProxy can be accessed from a per-SysUI process component, create a
// per-process listener to keep track of the current user id to reduce the number of binder
// calls to fetch it.
UserInfoController userInfoController = Dependency.get(UserInfoController.class);
userInfoController.addCallback(mOnUserInfoChangedListener);
-
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- // Create a dummy icon
- mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
- mDummyIcon.eraseColor(0xFF999999);
- }
-
- Collections.addAll(sRecentsBlacklist,
- res.getStringArray(R.array.recents_blacklist_array));
}
/**
@@ -377,110 +196,6 @@ public class SystemServicesProxy {
}
/**
- * @return whether the provided {@param className} is blacklisted
- */
- public boolean isBlackListedActivity(String className) {
- return sRecentsBlacklist.contains(className);
- }
-
- /**
- * Returns a list of the recents tasks.
- *
- * @param includeFrontMostExcludedTask if set, will ensure that the front most excluded task
- * will be visible, otherwise no excluded tasks will be
- * visible.
- */
- public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
- boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) {
- if (mAm == null) return null;
-
- // If we are mocking, then create some recent tasks
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- ArrayList<ActivityManager.RecentTaskInfo> tasks =
- new ArrayList<ActivityManager.RecentTaskInfo>();
- int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.MockTaskCount);
- for (int i = 0; i < count; i++) {
- // Create a dummy component name
- int packageIndex = i % RecentsDebugFlags.Static.MockTasksPackageCount;
- ComponentName cn = new ComponentName("com.android.test" + packageIndex,
- "com.android.test" + i + ".Activity");
- String description = "" + i + " - " +
- Long.toString(Math.abs(new Random().nextLong()), 36);
- // Create the recent task info
- ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
- rti.id = rti.persistentId = rti.affiliatedTaskId = i;
- rti.baseIntent = new Intent();
- rti.baseIntent.setComponent(cn);
- rti.description = description;
- rti.firstActiveTime = rti.lastActiveTime = i;
- if (i % 2 == 0) {
- rti.taskDescription = new ActivityManager.TaskDescription(description,
- Bitmap.createBitmap(mDummyIcon), null,
- 0xFF000000 | (0xFFFFFF & new Random().nextInt()),
- 0xFF000000 | (0xFFFFFF & new Random().nextInt()),
- 0, 0);
- } else {
- rti.taskDescription = new ActivityManager.TaskDescription();
- }
- tasks.add(rti);
- }
- return tasks;
- }
-
- // Remove home/recents/excluded tasks
- int minNumTasksToQuery = 10;
- int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
- int flags = ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS |
- ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK |
- ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS |
- ActivityManager.RECENT_IGNORE_UNAVAILABLE |
- ActivityManager.RECENT_INCLUDE_PROFILES;
- if (includeFrontMostExcludedTask) {
- flags |= ActivityManager.RECENT_WITH_EXCLUDED;
- }
- List<ActivityManager.RecentTaskInfo> tasks = null;
- try {
- tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId);
- } catch (Exception e) {
- Log.e(TAG, "Failed to get recent tasks", e);
- }
-
- // Break early if we can't get a valid set of tasks
- if (tasks == null) {
- return new ArrayList<>();
- }
-
- boolean isFirstValidTask = true;
- Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
- while (iter.hasNext()) {
- ActivityManager.RecentTaskInfo t = iter.next();
-
- // NOTE: The order of these checks happens in the expected order of the traversal of the
- // tasks
-
- // Remove the task if it or it's package are blacklsited
- if (sRecentsBlacklist.contains(t.realActivity.getClassName()) ||
- sRecentsBlacklist.contains(t.realActivity.getPackageName())) {
- iter.remove();
- continue;
- }
-
- // Remove the task if it is marked as excluded, unless it is the first most task and we
- // are requested to include it
- boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
- == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
- isExcluded |= quietProfileIds.contains(t.userId);
- if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) {
- iter.remove();
- }
-
- isFirstValidTask = false;
- }
-
- return tasks.subList(0, Math.min(tasks.size(), numLatestTasks));
- }
-
- /**
* Returns the top running task.
*/
public ActivityManager.RunningTaskInfo getRunningTask() {
@@ -572,13 +287,6 @@ public class SystemServicesProxy {
}
/**
- * Returns whether this device has freeform workspaces.
- */
- public boolean hasFreeformWorkspaceSupport() {
- return mHasFreeformWorkspaceSupport;
- }
-
- /**
* Returns whether this device is in the safe mode.
*/
public boolean isInSafeMode() {
@@ -646,7 +354,7 @@ public class SystemServicesProxy {
*/
public boolean hasSoftNavigationBar() {
try {
- return WindowManagerGlobal.getWindowManagerService().hasNavigationBar();
+ return mIwm.hasNavigationBar();
} catch (RemoteException e) {
e.printStackTrace();
}
@@ -689,43 +397,6 @@ public class SystemServicesProxy {
}
}
- /** Returns the top task thumbnail for the given task id */
- public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) {
- if (mAm == null) return null;
-
- // If we are mocking, then just return a dummy thumbnail
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- ThumbnailData thumbnailData = new ThumbnailData();
- thumbnailData.thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth,
- mDummyThumbnailHeight, Bitmap.Config.ARGB_8888);
- thumbnailData.thumbnail.eraseColor(0xff333333);
- return thumbnailData;
- }
-
- return getThumbnail(taskId, reduced);
- }
-
- /**
- * Returns a task thumbnail from the activity manager
- */
- public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) {
- if (mAm == null) {
- return new ThumbnailData();
- }
-
- ActivityManager.TaskSnapshot snapshot = null;
- try {
- snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to retrieve snapshot", e);
- }
- if (snapshot != null) {
- return ThumbnailData.createFromTaskSnapshot(snapshot);
- } else {
- return new ThumbnailData();
- }
- }
-
/** Set the task's windowing mode. */
public void setTaskWindowingMode(int taskId, int windowingMode) {
if (mIam == null) return;
@@ -740,11 +411,14 @@ public class SystemServicesProxy {
/** Removes the task */
public void removeTask(final int taskId) {
if (mAm == null) return;
- if (RecentsDebugFlags.Static.EnableMockTasks) return;
// Remove the task.
mUiOffloadThread.submit(() -> {
- mAm.removeTask(taskId);
+ try {
+ mIam.removeTask(taskId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
});
}
@@ -760,145 +434,6 @@ public class SystemServicesProxy {
});
}
- /**
- * Returns the activity info for a given component name.
- *
- * @param cn The component name of the activity.
- * @param userId The userId of the user that this is for.
- */
- public ActivityInfo getActivityInfo(ComponentName cn, int userId) {
- if (mIpm == null) return null;
- if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo();
-
- try {
- return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId);
- } catch (RemoteException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * Returns the activity info for a given component name.
- *
- * @param cn The component name of the activity.
- */
- public ActivityInfo getActivityInfo(ComponentName cn) {
- if (mPm == null) return null;
- if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo();
-
- try {
- return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA);
- } catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * Returns the activity label, badging if necessary.
- */
- public String getBadgedActivityLabel(ActivityInfo info, int userId) {
- if (mPm == null) return null;
-
- // If we are mocking, then return a mock label
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return "Recent Task: " + userId;
- }
-
- return getBadgedLabel(info.loadLabel(mPm).toString(), userId);
- }
-
- /**
- * Returns the application label, badging if necessary.
- */
- public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) {
- if (mPm == null) return null;
-
- // If we are mocking, then return a mock label
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return "Recent Task App: " + userId;
- }
-
- return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId);
- }
-
- /**
- * Returns the content description for a given task, badging it if necessary. The content
- * description joins the app and activity labels.
- */
- public String getBadgedContentDescription(ActivityInfo info, int userId,
- ActivityManager.TaskDescription td, Resources res) {
- // If we are mocking, then return a mock label
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return "Recent Task Content Description: " + userId;
- }
-
- String activityLabel;
- if (td != null && td.getLabel() != null) {
- activityLabel = td.getLabel();
- } else {
- activityLabel = info.loadLabel(mPm).toString();
- }
- String applicationLabel = info.applicationInfo.loadLabel(mPm).toString();
- String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
- return applicationLabel.equals(activityLabel) ? badgedApplicationLabel
- : res.getString(R.string.accessibility_recents_task_header,
- badgedApplicationLabel, activityLabel);
- }
-
- /**
- * Returns the activity icon for the ActivityInfo for a user, badging if
- * necessary.
- */
- public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) {
- if (mPm == null) return null;
-
- // If we are mocking, then return a mock label
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return new ColorDrawable(0xFF666666);
- }
-
- return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId);
- }
-
- /**
- * Returns the application icon for the ApplicationInfo for a user, badging if
- * necessary.
- */
- public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) {
- if (mPm == null) return null;
-
- // If we are mocking, then return a mock label
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return new ColorDrawable(0xFF666666);
- }
-
- return mDrawableFactory.getBadgedIcon(appInfo, userId);
- }
-
- /**
- * Returns the task description icon, loading and badging it if it necessary.
- */
- public Drawable getBadgedTaskDescriptionIcon(ActivityManager.TaskDescription taskDescription,
- int userId, Resources res) {
-
- // If we are mocking, then return a mock label
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return new ColorDrawable(0xFF666666);
- }
-
- Bitmap tdIcon = taskDescription.getInMemoryIcon();
- if (tdIcon == null) {
- tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon(
- taskDescription.getIconFilename(), userId);
- }
- if (tdIcon != null) {
- return getBadgedIcon(new BitmapDrawable(res, tdIcon), userId);
- }
- return null;
- }
-
public ActivityManager.TaskDescription getTaskDescription(int taskId) {
try {
return mIam.getTaskDescription(taskId);
@@ -908,85 +443,6 @@ public class SystemServicesProxy {
}
/**
- * Returns the given icon for a user, badging if necessary.
- */
- private Drawable getBadgedIcon(Drawable icon, int userId) {
- if (userId != UserHandle.myUserId()) {
- icon = mPm.getUserBadgedIcon(icon, new UserHandle(userId));
- }
- return icon;
- }
-
- /**
- * Returns a banner used on TV for the specified Activity.
- */
- public Drawable getActivityBanner(ActivityInfo info) {
- if (mPm == null) return null;
-
- // If we are mocking, then return a mock banner
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return new ColorDrawable(0xFF666666);
- }
-
- Drawable banner = info.loadBanner(mPm);
- return banner;
- }
-
- /**
- * Returns a logo used on TV for the specified Activity.
- */
- public Drawable getActivityLogo(ActivityInfo info) {
- if (mPm == null) return null;
-
- // If we are mocking, then return a mock logo
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- return new ColorDrawable(0xFF666666);
- }
-
- Drawable logo = info.loadLogo(mPm);
- return logo;
- }
-
-
- /**
- * Returns the given label for a user, badging if necessary.
- */
- private String getBadgedLabel(String label, int userId) {
- if (userId != UserHandle.myUserId()) {
- label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString();
- }
- return label;
- }
-
- /**
- * Returns whether the provided {@param userId} is currently locked (and showing Keyguard).
- */
- public boolean isDeviceLocked(int userId) {
- if (mKgm == null) {
- return false;
- }
- return mKgm.isDeviceLocked(userId);
- }
-
- /** Returns the package name of the home activity. */
- public String getHomeActivityPackageName() {
- if (mPm == null) return null;
- if (RecentsDebugFlags.Static.EnableMockTasks) return null;
-
- ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
- ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities);
- if (defaultHomeActivity != null) {
- return defaultHomeActivity.getPackageName();
- } else if (homeActivities.size() == 1) {
- ResolveInfo info = homeActivities.get(0);
- if (info.activityInfo != null) {
- return info.activityInfo.packageName;
- }
- }
- return null;
- }
-
- /**
* Returns whether the provided {@param userId} represents the system user.
*/
public boolean isSystemUser(int userId) {
@@ -1091,7 +547,7 @@ public class SystemServicesProxy {
ActivityManager.StackInfo stackInfo =
mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
if (stackInfo == null) {
- stackInfo = mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
+ stackInfo = mIam.getStackInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
}
if (stackInfo != null) {
windowRect.set(stackInfo.bounds);
@@ -1174,19 +630,11 @@ public class SystemServicesProxy {
* Registers a task stack listener with the system.
* This should be called on the main thread.
*/
- public void registerTaskStackListener(TaskStackListener listener) {
+ public void registerTaskStackListener(TaskStackChangeListener listener) {
if (mIam == null) return;
- synchronized (mTaskStackListeners) {
- mTaskStackListeners.add(listener);
- if (mTaskStackListeners.size() == 1) {
- // Register mTaskStackListener to IActivityManager only once if needed.
- try {
- mIam.registerTaskStackListener(mTaskStackListener);
- } catch (Exception e) {
- Log.w(TAG, "Failed to call registerTaskStackListener", e);
- }
- }
+ synchronized (mTaskStackChangeListeners) {
+ mTaskStackChangeListeners.addListener(mIam, listener);
}
}
@@ -1195,7 +643,7 @@ public class SystemServicesProxy {
return;
}
try {
- WindowManagerGlobal.getWindowManagerService().endProlongedAnimations();
+ mIwm.endProlongedAnimations();
} catch (Exception e) {
e.printStackTrace();
}
@@ -1205,7 +653,7 @@ public class SystemServicesProxy {
if (mWm == null) return;
try {
- WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(listener);
+ mIwm.registerDockedStackListener(listener);
} catch (Exception e) {
e.printStackTrace();
}
@@ -1232,8 +680,7 @@ public class SystemServicesProxy {
if (mWm == null) return;
try {
- WindowManagerGlobal.getWindowManagerService().getStableInsets(Display.DEFAULT_DISPLAY,
- outStableInsets);
+ mIwm.getStableInsets(Display.DEFAULT_DISPLAY, outStableInsets);
} catch (Exception e) {
e.printStackTrace();
}
@@ -1243,9 +690,7 @@ public class SystemServicesProxy {
IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener,
boolean scaleUp) {
try {
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionMultiThumbFuture(future, animStartedListener,
- scaleUp);
+ mIwm.overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, scaleUp);
} catch (RemoteException e) {
Log.w(TAG, "Failed to override transition: " + e);
}
@@ -1254,23 +699,27 @@ public class SystemServicesProxy {
/**
* Updates the visibility of recents.
*/
- public void setRecentsVisibility(boolean visible) {
- try {
- mIwm.setRecentsVisibility(visible);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to reach window manager", e);
- }
+ public void setRecentsVisibility(final boolean visible) {
+ mUiOffloadThread.submit(() -> {
+ try {
+ mIwm.setRecentsVisibility(visible);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to reach window manager", e);
+ }
+ });
}
/**
* Updates the visibility of the picture-in-picture.
*/
- public void setPipVisibility(boolean visible) {
- try {
- mIwm.setPipVisibility(visible);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to reach window manager", e);
- }
+ public void setPipVisibility(final boolean visible) {
+ mUiOffloadThread.submit(() -> {
+ try {
+ mIwm.setPipVisibility(visible);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to reach window manager", e);
+ }
+ });
}
public boolean isDreaming() {
@@ -1292,126 +741,7 @@ public class SystemServicesProxy {
});
}
- public void updateOverviewLastStackActiveTimeAsync(long newLastStackActiveTime,
- int currentUserId) {
- mUiOffloadThread.submit(() -> {
- Settings.Secure.putLongForUser(mContext.getContentResolver(),
- Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId);
- });
- }
-
public interface StartActivityFromRecentsResultListener {
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;
- private static final int ON_ACTIVITY_PINNED = 3;
- private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4;
- private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5;
- private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6;
- private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
- private static final int ON_TASK_PROFILE_LOCKED = 8;
- private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
- private static final int ON_ACTIVITY_UNPINNED = 10;
- private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11;
-
- @Override
- public void handleMessage(Message msg) {
- synchronized (mTaskStackListeners) {
- switch (msg.what) {
- case ON_TASK_STACK_CHANGED: {
- Trace.beginSection("onTaskStackChanged");
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onTaskStackChanged();
- }
- Trace.endSection();
- break;
- }
- case ON_TASK_SNAPSHOT_CHANGED: {
- Trace.beginSection("onTaskSnapshotChanged");
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1,
- (TaskSnapshot) msg.obj);
- }
- Trace.endSection();
- break;
- }
- case ON_ACTIVITY_PINNED: {
- final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj;
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityPinned(
- info.mPackageName, info.mUserId, info.mTaskId, info.mStackId);
- }
- break;
- }
- case ON_ACTIVITY_UNPINNED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityUnpinned();
- }
- break;
- }
- case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onPinnedActivityRestartAttempt(
- msg.arg1 != 0);
- }
- break;
- }
- case ON_PINNED_STACK_ANIMATION_STARTED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onPinnedStackAnimationStarted();
- }
- break;
- }
- case ON_PINNED_STACK_ANIMATION_ENDED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onPinnedStackAnimationEnded();
- }
- break;
- }
- case ON_ACTIVITY_FORCED_RESIZABLE: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityForcedResizable(
- (String) msg.obj, msg.arg1, msg.arg2);
- }
- break;
- }
- case ON_ACTIVITY_DISMISSING_DOCKED_STACK: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityDismissingDockedStack();
- }
- break;
- }
- case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed();
- }
- break;
- }
- case ON_TASK_PROFILE_LOCKED: {
- for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
- }
- break;
- }
- }
- }
- }
- }
}
diff --git a/com/android/systemui/recents/misc/TaskStackChangeListener.java b/com/android/systemui/recents/misc/TaskStackChangeListener.java
new file mode 100644
index 00000000..6d0952ab
--- /dev/null
+++ b/com/android/systemui/recents/misc/TaskStackChangeListener.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 com.android.systemui.recents.misc;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.content.Context;
+import android.os.UserHandle;
+import android.util.Log;
+
+/**
+ * An abstract class to track task stack changes.
+ * Classes should implement this instead of {@link android.app.ITaskStackListener}
+ * to reduce IPC calls from system services. These callbacks will be called on the main thread.
+ */
+public abstract class TaskStackChangeListener {
+
+ /**
+ * NOTE: This call is made of the thread that the binder call comes in on.
+ */
+ public void onTaskStackChangedBackground() { }
+ public void onTaskStackChanged() { }
+ public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
+ public void onActivityUnpinned() { }
+ public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
+ public void onPinnedStackAnimationStarted() { }
+ public void onPinnedStackAnimationEnded() { }
+ public void onActivityForcedResizable(String packageName, int taskId, int reason) { }
+ public void onActivityDismissingDockedStack() { }
+ public void onActivityLaunchOnSecondaryDisplayFailed() { }
+ public void onTaskProfileLocked(int taskId, int userId) { }
+
+ /**
+ * Checks that the current user matches the user's SystemUI process. Since
+ * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
+ * TaskStackChangeListener should make this call to verify that we don't act on events from other
+ * user's processes.
+ */
+ protected final boolean checkCurrentUserId(Context context, boolean debug) {
+ int processUserId = UserHandle.myUserId();
+ int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
+ if (processUserId != currentUserId) {
+ if (debug) {
+ Log.d(SystemServicesProxy.TAG, "UID mismatch. SystemUI is running uid=" + processUserId
+ + " and the current user is uid=" + currentUserId);
+ }
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/com/android/systemui/recents/misc/TaskStackChangeListeners.java b/com/android/systemui/recents/misc/TaskStackChangeListeners.java
new file mode 100644
index 00000000..8eb70f04
--- /dev/null
+++ b/com/android/systemui/recents/misc/TaskStackChangeListeners.java
@@ -0,0 +1,254 @@
+/*
+ * 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.recents.misc;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.app.IActivityManager;
+import android.app.TaskStackListener;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tracks all the task stack listeners
+ */
+public class TaskStackChangeListeners extends TaskStackListener {
+
+ private static final String TAG = TaskStackChangeListeners.class.getSimpleName();
+
+ /**
+ * List of {@link TaskStackChangeListener} registered from {@link #addListener}.
+ */
+ private final List<TaskStackChangeListener> mTaskStackListeners = new ArrayList<>();
+ private final List<TaskStackChangeListener> mTmpListeners = new ArrayList<>();
+
+ private final Handler mHandler;
+
+ public TaskStackChangeListeners(Looper looper) {
+ mHandler = new H(looper);
+ }
+
+ public void addListener(IActivityManager am, TaskStackChangeListener listener) {
+ mTaskStackListeners.add(listener);
+ if (mTaskStackListeners.size() == 1) {
+ // Register mTaskStackListener to IActivityManager only once if needed.
+ try {
+ am.registerTaskStackListener(this);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to call registerTaskStackListener", e);
+ }
+ }
+ }
+
+ @Override
+ public void onTaskStackChanged() throws RemoteException {
+ // Call the task changed callback for the non-ui thread listeners first
+ synchronized (mTaskStackListeners) {
+ mTmpListeners.clear();
+ mTmpListeners.addAll(mTaskStackListeners);
+ }
+ for (int i = mTmpListeners.size() - 1; i >= 0; i--) {
+ mTmpListeners.get(i).onTaskStackChangedBackground();
+ }
+
+ mHandler.removeMessages(H.ON_TASK_STACK_CHANGED);
+ mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED);
+ }
+
+ @Override
+ 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,
+ new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget();
+ }
+
+ @Override
+ public void onActivityUnpinned() throws RemoteException {
+ mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
+ mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
+ }
+
+ @Override
+ public void onPinnedActivityRestartAttempt(boolean clearedTask)
+ throws RemoteException{
+ mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
+ mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, clearedTask ? 1 : 0, 0)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onPinnedStackAnimationStarted() throws RemoteException {
+ mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED);
+ mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED);
+ }
+
+ @Override
+ public void onPinnedStackAnimationEnded() throws RemoteException {
+ mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
+ mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
+ }
+
+ @Override
+ public void onActivityForcedResizable(String packageName, int taskId, int reason)
+ throws RemoteException {
+ mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onActivityDismissingDockedStack() throws RemoteException {
+ mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK);
+ }
+
+ @Override
+ public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException {
+ mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED);
+ }
+
+ @Override
+ public void onTaskProfileLocked(int taskId, int userId) throws RemoteException {
+ mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
+ }
+
+ @Override
+ public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
+ throws RemoteException {
+ mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
+ }
+
+ private final class H extends Handler {
+ private static final int ON_TASK_STACK_CHANGED = 1;
+ private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
+ private static final int ON_ACTIVITY_PINNED = 3;
+ private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4;
+ private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5;
+ private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6;
+ private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
+ private static final int ON_TASK_PROFILE_LOCKED = 8;
+ private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
+ private static final int ON_ACTIVITY_UNPINNED = 10;
+ private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11;
+
+ public H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ synchronized (mTaskStackListeners) {
+ switch (msg.what) {
+ case ON_TASK_STACK_CHANGED: {
+ Trace.beginSection("onTaskStackChanged");
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onTaskStackChanged();
+ }
+ Trace.endSection();
+ break;
+ }
+ case ON_TASK_SNAPSHOT_CHANGED: {
+ Trace.beginSection("onTaskSnapshotChanged");
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1,
+ (TaskSnapshot) msg.obj);
+ }
+ Trace.endSection();
+ break;
+ }
+ case ON_ACTIVITY_PINNED: {
+ final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj;
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onActivityPinned(
+ info.mPackageName, info.mUserId, info.mTaskId, info.mStackId);
+ }
+ break;
+ }
+ case ON_ACTIVITY_UNPINNED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onActivityUnpinned();
+ }
+ break;
+ }
+ case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onPinnedActivityRestartAttempt(
+ msg.arg1 != 0);
+ }
+ break;
+ }
+ case ON_PINNED_STACK_ANIMATION_STARTED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onPinnedStackAnimationStarted();
+ }
+ break;
+ }
+ case ON_PINNED_STACK_ANIMATION_ENDED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onPinnedStackAnimationEnded();
+ }
+ break;
+ }
+ case ON_ACTIVITY_FORCED_RESIZABLE: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onActivityForcedResizable(
+ (String) msg.obj, msg.arg1, msg.arg2);
+ }
+ break;
+ }
+ case ON_ACTIVITY_DISMISSING_DOCKED_STACK: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onActivityDismissingDockedStack();
+ }
+ break;
+ }
+ case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed();
+ }
+ break;
+ }
+ case ON_TASK_PROFILE_LOCKED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private static 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;
+ }
+ }
+}
diff --git a/com/android/systemui/recents/model/RecentsPackageMonitor.java b/com/android/systemui/recents/model/RecentsPackageMonitor.java
deleted file mode 100644
index 308cece1..00000000
--- a/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ /dev/null
@@ -1,74 +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.systemui.recents.model;
-
-import android.content.Context;
-import android.os.UserHandle;
-
-import com.android.internal.content.PackageMonitor;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.misc.ForegroundThread;
-
-/**
- * The package monitor listens for changes from PackageManager to update the contents of the
- * Recents list.
- */
-public class RecentsPackageMonitor extends PackageMonitor {
-
- /** Registers the broadcast receivers with the specified callbacks. */
- public void register(Context context) {
- try {
- // We register for events from all users, but will cross-reference them with
- // packages for the current user and any profiles they have. Ensure that events are
- // handled in a background thread.
- register(context, ForegroundThread.get().getLooper(), UserHandle.ALL, true);
- } catch (IllegalStateException e) {
- e.printStackTrace();
- }
- }
-
- /** Unregisters the broadcast receivers. */
- @Override
- public void unregister() {
- try {
- super.unregister();
- } catch (IllegalStateException e) {
- e.printStackTrace();
- }
- }
-
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- // Notify callbacks on the main thread that a package has changed
- final int eventUserId = getChangingUserId();
- EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
- }
-
- @Override
- public boolean onPackageChanged(String packageName, int uid, String[] components) {
- onPackageModified(packageName);
- return true;
- }
-
- @Override
- public void onPackageModified(String packageName) {
- // Notify callbacks on the main thread that a package has changed
- final int eventUserId = getChangingUserId();
- EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
- }
-}
diff --git a/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
deleted file mode 100644
index d5e03135..00000000
--- a/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ /dev/null
@@ -1,330 +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.systemui.recents.model;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.util.ArraySet;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
-import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-
-/**
- * This class stores the loading state as it goes through multiple stages of loading:
- * 1) preloadRawTasks() will load the raw set of recents tasks from the system
- * 2) preloadPlan() will construct a new task stack with all metadata and only icons and
- * thumbnails that are currently in the cache
- * 3) executePlan() will actually load and fill in the icons and thumbnails according to the load
- * options specified, such that we can transition into the Recents activity seamlessly
- */
-public class RecentsTaskLoadPlan {
-
- private static int MIN_NUM_TASKS = 5;
- private static int SESSION_BEGIN_TIME = 1000 /* ms/s */ * 60 /* s/min */ * 60 /* min/hr */ *
- 6 /* hrs */;
-
- /** The set of conditions to load tasks. */
- public static class Options {
- public int runningTaskId = -1;
- public boolean loadIcons = true;
- public boolean loadThumbnails = false;
- public boolean onlyLoadForCache = false;
- public boolean onlyLoadPausedActivities = false;
- public int numVisibleTasks = 0;
- public int numVisibleTaskThumbnails = 0;
- }
-
- Context mContext;
-
- int mPreloadedUserId;
- List<ActivityManager.RecentTaskInfo> mRawTasks;
- TaskStack mStack;
- ArraySet<Integer> mCurrentQuietProfiles = new ArraySet<Integer>();
-
- /** Package level ctor */
- RecentsTaskLoadPlan(Context context) {
- mContext = context;
- }
-
- private void updateCurrentQuietProfilesCache(int currentUserId) {
- mCurrentQuietProfiles.clear();
-
- UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- List<UserInfo> profiles = userManager.getProfiles(currentUserId);
- if (profiles != null) {
- for (int i = 0; i < profiles.size(); i++) {
- UserInfo user = profiles.get(i);
- if (user.isManagedProfile() && user.isQuietModeEnabled()) {
- mCurrentQuietProfiles.add(user.id);
- }
- }
- }
- }
-
- /**
- * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
- * to most-recent order.
- *
- * Note: Do not lock, callers should synchronize on the loader before making this call.
- */
- void preloadRawTasks(boolean includeFrontMostExcludedTask) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- int currentUserId = ssp.getCurrentUser();
- updateCurrentQuietProfilesCache(currentUserId);
- mPreloadedUserId = currentUserId;
- mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
- currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles);
-
- // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
- Collections.reverse(mRawTasks);
- }
-
- /**
- * Preloads the list of recent tasks from the system. After this call, the TaskStack will
- * have a list of all the recent tasks with their metadata, not including icons or
- * thumbnails which were not cached and have to be loaded.
- *
- * The tasks will be ordered by:
- * - least-recent to most-recent stack tasks
- * - least-recent to most-recent freeform tasks
- *
- * Note: Do not lock, since this can be calling back to the loader, which separately also drives
- * this call (callers should synchronize on the loader before making this call).
- */
- void preloadPlan(RecentsTaskLoader loader, int runningTaskId,
- boolean includeFrontMostExcludedTask) {
- Resources res = mContext.getResources();
- ArrayList<Task> allTasks = new ArrayList<>();
- if (mRawTasks == null) {
- preloadRawTasks(includeFrontMostExcludedTask);
- }
-
- SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
- SparseIntArray affiliatedTaskCounts = new SparseIntArray();
- SparseBooleanArray lockedUsers = new SparseBooleanArray();
- String dismissDescFormat = mContext.getString(
- R.string.accessibility_recents_item_will_be_dismissed);
- String appInfoDescFormat = mContext.getString(
- R.string.accessibility_recents_item_open_app_info);
- int currentUserId = mPreloadedUserId;
- long legacyLastStackActiveTime = migrateLegacyLastStackActiveTime(currentUserId);
- long lastStackActiveTime = Settings.Secure.getLongForUser(mContext.getContentResolver(),
- Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime, currentUserId);
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- lastStackActiveTime = 0;
- }
- long newLastStackActiveTime = -1;
- int taskCount = mRawTasks.size();
- for (int i = 0; i < taskCount; i++) {
- ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
-
- // Compose the task key
- 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 = windowingMode == WINDOWING_MODE_FREEFORM;
- boolean isStackTask;
- if (Recents.getConfiguration().isGridEnabled) {
- // When grid layout is enabled, we only show the first
- // TaskGridLayoutAlgorithm.MAX_LAYOUT_FROM_HOME_TASK_COUNT} tasks.
- isStackTask = t.lastActiveTime >= lastStackActiveTime &&
- i >= taskCount - TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
- } else if (Recents.getConfiguration().isLowRamDevice) {
- // Show a max of 3 items
- isStackTask = t.lastActiveTime >= lastStackActiveTime &&
- i >= taskCount - TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
- } else {
- isStackTask = isFreeformTask || !isHistoricalTask(t) ||
- (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS));
- }
- boolean isLaunchTarget = taskKey.id == runningTaskId;
-
- // The last stack active time is the baseline for which we show visible tasks. Since
- // the system will store all the tasks, we don't want to show the tasks prior to the
- // last visible ones, otherwise, as you dismiss them, the previous tasks may satisfy
- // the other stack-task constraints.
- if (isStackTask && newLastStackActiveTime < 0) {
- newLastStackActiveTime = t.lastActiveTime;
- }
-
- // Load the title, icon, and color
- ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
- String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
- String titleDescription = loader.getAndUpdateContentDescription(taskKey,
- t.taskDescription, res);
- String dismissDescription = String.format(dismissDescFormat, titleDescription);
- String appInfoDescription = String.format(appInfoDescFormat, titleDescription);
- Drawable icon = isStackTask
- ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
- : null;
- ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
- false /* loadIfNotCached */, false /* storeInCache */);
- int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
- int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
- boolean isSystemApp = (info != null) &&
- ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
- if (lockedUsers.indexOfKey(t.userId) < 0) {
- lockedUsers.put(t.userId, Recents.getSystemServices().isDeviceLocked(t.userId));
- }
- boolean isLocked = lockedUsers.get(t.userId);
-
- // Add the task to the stack
- Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
- thumbnail, title, titleDescription, dismissDescription, appInfoDescription,
- activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp,
- t.supportsSplitScreenMultiWindow, t.bounds, t.taskDescription, t.resizeMode, t.topActivity,
- isLocked);
-
- allTasks.add(task);
- affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
- affiliatedTasks.put(taskKey.id, taskKey);
- }
- if (newLastStackActiveTime != -1) {
- Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
- newLastStackActiveTime, currentUserId);
- }
-
- // Initialize the stacks
- mStack = new TaskStack();
- mStack.setTasks(mContext, allTasks, false /* notifyStackChanges */);
- }
-
- /**
- * Called to apply the actual loading based on the specified conditions.
- *
- * Note: Do not lock, since this can be calling back to the loader, which separately also drives
- * this call (callers should synchronize on the loader before making this call).
- */
- void executePlan(Options opts, RecentsTaskLoader loader) {
- Resources res = mContext.getResources();
-
- // Iterate through each of the tasks and load them according to the load conditions.
- ArrayList<Task> tasks = mStack.getStackTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- Task.TaskKey taskKey = task.key;
-
- boolean isRunningTask = (task.key.id == opts.runningTaskId);
- boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
- boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
-
- // If requested, skip the running task
- if (opts.onlyLoadPausedActivities && isRunningTask) {
- continue;
- }
-
- if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
- if (task.icon == null) {
- task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res,
- true);
- }
- }
- if (opts.loadThumbnails && isVisibleThumbnail) {
- task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
- true /* loadIfNotCached */, true /* storeInCache */);
- }
- }
- }
-
- /**
- * Returns the TaskStack from the preloaded list of recent tasks.
- */
- public TaskStack getTaskStack() {
- return mStack;
- }
-
- /**
- * Returns the raw list of recent tasks.
- */
- public List<ActivityManager.RecentTaskInfo> getRawTasks() {
- return mRawTasks;
- }
-
- /** Returns whether there are any tasks in any stacks. */
- public boolean hasTasks() {
- if (mStack != null) {
- return mStack.getTaskCount() > 0;
- }
- return false;
- }
-
- /**
- * Returns whether this task is too old to be shown.
- */
- private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) {
- return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME);
- }
-
-
- /**
- * Migrate the last active time from the prefs to the secure settings.
- *
- * The first time this runs, it will:
- * 1) fetch the last stack active time from the prefs
- * 2) set the prefs to the last stack active time for all users
- * 3) clear the pref
- * 4) return the last stack active time
- *
- * Subsequent calls to this will return zero.
- */
- private long migrateLegacyLastStackActiveTime(int currentUserId) {
- long legacyLastStackActiveTime = Prefs.getLong(mContext,
- Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1);
- if (legacyLastStackActiveTime != -1) {
- Prefs.remove(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME);
- UserManager userMgr = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- List<UserInfo> users = userMgr.getUsers();
- for (int i = 0; i < users.size(); i++) {
- int userId = users.get(i).id;
- if (userId != currentUserId) {
- Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
- legacyLastStackActiveTime, userId);
- }
- }
- return legacyLastStackActiveTime;
- }
- return 0;
- }
-}
diff --git a/com/android/systemui/recents/model/TaskGrouping.java b/com/android/systemui/recents/model/TaskGrouping.java
deleted file mode 100644
index 2109376d..00000000
--- a/com/android/systemui/recents/model/TaskGrouping.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package com.android.systemui.recents.model;
-
-import android.util.ArrayMap;
-
-import java.util.ArrayList;
-
-/** Represents a grouping of tasks witihin a stack. */
-public class TaskGrouping {
-
- int affiliation;
- long latestActiveTimeInGroup;
-
- Task.TaskKey mFrontMostTaskKey;
- ArrayList<Task.TaskKey> mTaskKeys = new ArrayList<Task.TaskKey>();
- ArrayMap<Task.TaskKey, Integer> mTaskKeyIndices = new ArrayMap<>();
-
- /** Creates a group with a specified affiliation. */
- public TaskGrouping(int affiliation) {
- this.affiliation = affiliation;
- }
-
- /** Adds a new task to this group. */
- void addTask(Task t) {
- mTaskKeys.add(t.key);
- if (t.key.lastActiveTime > latestActiveTimeInGroup) {
- latestActiveTimeInGroup = t.key.lastActiveTime;
- }
- t.setGroup(this);
- updateTaskIndices();
- }
-
- /** Removes a task from this group. */
- void removeTask(Task t) {
- mTaskKeys.remove(t.key);
- latestActiveTimeInGroup = 0;
- int taskCount = mTaskKeys.size();
- for (int i = 0; i < taskCount; i++) {
- long lastActiveTime = mTaskKeys.get(i).lastActiveTime;
- if (lastActiveTime > latestActiveTimeInGroup) {
- latestActiveTimeInGroup = lastActiveTime;
- }
- }
- t.setGroup(null);
- updateTaskIndices();
- }
-
- /** Returns the key of the next task in the group. */
- public Task.TaskKey getNextTaskInGroup(Task t) {
- int i = indexOf(t);
- if ((i + 1) < getTaskCount()) {
- return mTaskKeys.get(i + 1);
- }
- return null;
- }
-
- /** Returns the key of the previous task in the group. */
- public Task.TaskKey getPrevTaskInGroup(Task t) {
- int i = indexOf(t);
- if ((i - 1) >= 0) {
- return mTaskKeys.get(i - 1);
- }
- return null;
- }
-
- /** Gets the front task */
- public boolean isFrontMostTask(Task t) {
- return (t.key == mFrontMostTaskKey);
- }
-
- /** Finds the index of a given task in a group. */
- public int indexOf(Task t) {
- return mTaskKeyIndices.get(t.key);
- }
-
- /** Returns whether a task is in this grouping. */
- public boolean containsTask(Task t) {
- return mTaskKeyIndices.containsKey(t.key);
- }
-
- /** Returns whether one task is above another in the group. If they are not in the same group,
- * this returns false. */
- public boolean isTaskAboveTask(Task t, Task below) {
- return mTaskKeyIndices.containsKey(t.key) && mTaskKeyIndices.containsKey(below.key) &&
- mTaskKeyIndices.get(t.key) > mTaskKeyIndices.get(below.key);
- }
-
- /** Returns the number of tasks in this group. */
- public int getTaskCount() { return mTaskKeys.size(); }
-
- /** Updates the mapping of tasks to indices. */
- private void updateTaskIndices() {
- if (mTaskKeys.isEmpty()) {
- mFrontMostTaskKey = null;
- mTaskKeyIndices.clear();
- return;
- }
-
- int taskCount = mTaskKeys.size();
- mFrontMostTaskKey = mTaskKeys.get(mTaskKeys.size() - 1);
- mTaskKeyIndices.clear();
- for (int i = 0; i < taskCount; i++) {
- Task.TaskKey k = mTaskKeys.get(i);
- mTaskKeyIndices.put(k, i);
- }
- }
-}
diff --git a/com/android/systemui/recents/model/TaskStack.java b/com/android/systemui/recents/model/TaskStack.java
deleted file mode 100644
index fdae917c..00000000
--- a/com/android/systemui/recents/model/TaskStack.java
+++ /dev/null
@@ -1,1140 +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.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.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;
-import static android.view.WindowManager.DOCKED_RIGHT;
-import static android.view.WindowManager.DOCKED_TOP;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.annotation.IntDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ColorDrawable;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.IntProperty;
-import android.util.SparseArray;
-import android.view.animation.Interpolator;
-
-import com.android.internal.policy.DockedDividerUtils;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.misc.NamedCounter;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.views.AnimationProps;
-import com.android.systemui.recents.views.DropTarget;
-import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
-
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Random;
-
-
-/**
- * An interface for a task filter to query whether a particular task should show in a stack.
- */
-interface TaskFilter {
- /** Returns whether the filter accepts the specified task */
- public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index);
-}
-
-/**
- * A list of filtered tasks.
- */
-class FilteredTaskList {
-
- ArrayList<Task> mTasks = new ArrayList<>();
- ArrayList<Task> mFilteredTasks = new ArrayList<>();
- ArrayMap<Task.TaskKey, Integer> mTaskIndices = new ArrayMap<>();
- TaskFilter mFilter;
-
- /** Sets the task filter, saving the current touch state */
- boolean setFilter(TaskFilter filter) {
- ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks);
- mFilter = filter;
- updateFilteredTasks();
- if (!prevFilteredTasks.equals(mFilteredTasks)) {
- return true;
- } else {
- return false;
- }
- }
-
- /** Removes the task filter and returns the previous touch state */
- void removeFilter() {
- mFilter = null;
- updateFilteredTasks();
- }
-
- /** Adds a new task to the task list */
- void add(Task t) {
- mTasks.add(t);
- updateFilteredTasks();
- }
-
- /**
- * Moves the given task.
- */
- public void setTaskWindowingMode(Task task, int insertIndex, int windowingMode) {
- int taskIndex = indexOf(task);
- if (taskIndex != insertIndex) {
- mTasks.remove(taskIndex);
- if (taskIndex < insertIndex) {
- insertIndex--;
- }
- mTasks.add(insertIndex, task);
- }
-
- // Update the stack id now, after we've moved the task, and before we update the
- // filtered tasks
- task.setWindowingMode(windowingMode);
- updateFilteredTasks();
- }
-
- /** Sets the list of tasks */
- void set(List<Task> tasks) {
- mTasks.clear();
- mTasks.addAll(tasks);
- updateFilteredTasks();
- }
-
- /** Removes a task from the base list only if it is in the filtered list */
- boolean remove(Task t) {
- if (mFilteredTasks.contains(t)) {
- boolean removed = mTasks.remove(t);
- updateFilteredTasks();
- return removed;
- }
- return false;
- }
-
- /** Returns the index of this task in the list of filtered tasks */
- int indexOf(Task t) {
- if (t != null && mTaskIndices.containsKey(t.key)) {
- return mTaskIndices.get(t.key);
- }
- return -1;
- }
-
- /** Returns the size of the list of filtered tasks */
- int size() {
- return mFilteredTasks.size();
- }
-
- /** Returns whether the filtered list contains this task */
- boolean contains(Task t) {
- return mTaskIndices.containsKey(t.key);
- }
-
- /** Updates the list of filtered tasks whenever the base task list changes */
- private void updateFilteredTasks() {
- mFilteredTasks.clear();
- if (mFilter != null) {
- // Create a sparse array from task id to Task
- SparseArray<Task> taskIdMap = new SparseArray<>();
- int taskCount = mTasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task t = mTasks.get(i);
- taskIdMap.put(t.key.id, t);
- }
-
- for (int i = 0; i < taskCount; i++) {
- Task t = mTasks.get(i);
- if (mFilter.acceptTask(taskIdMap, t, i)) {
- mFilteredTasks.add(t);
- }
- }
- } else {
- mFilteredTasks.addAll(mTasks);
- }
- updateFilteredTaskIndices();
- }
-
- /** Updates the mapping of tasks to indices. */
- private void updateFilteredTaskIndices() {
- int taskCount = mFilteredTasks.size();
- mTaskIndices.clear();
- for (int i = 0; i < taskCount; i++) {
- Task t = mFilteredTasks.get(i);
- mTaskIndices.put(t.key, i);
- }
- }
-
- /** Returns whether this task list is filtered */
- boolean hasFilter() {
- return (mFilter != null);
- }
-
- /** Returns the list of filtered tasks */
- ArrayList<Task> getTasks() {
- return mFilteredTasks;
- }
-}
-
-/**
- * The task stack contains a list of multiple tasks.
- */
-public class TaskStack {
-
- private static final String TAG = "TaskStack";
-
- /** Task stack callbacks */
- public interface TaskStackCallbacks {
- /**
- * Notifies when a new task has been added to the stack.
- */
- void onStackTaskAdded(TaskStack stack, Task newTask);
-
- /**
- * Notifies when a task has been removed from the stack.
- */
- void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
- AnimationProps animation, boolean fromDockGesture,
- boolean dismissRecentsIfAllRemoved);
-
- /**
- * Notifies when all tasks have been removed from the stack.
- */
- void onStackTasksRemoved(TaskStack stack);
-
- /**
- * Notifies when tasks in the stack have been updated.
- */
- void onStackTasksUpdated(TaskStack stack);
- }
-
- /**
- * The various possible dock states when dragging and dropping a task.
- */
- public static class DockState implements DropTarget {
-
- public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
- public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
-
- // The rotation to apply to the hint text
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({HORIZONTAL, VERTICAL})
- public @interface TextOrientation {}
- private static final int HORIZONTAL = 0;
- private static final int VERTICAL = 1;
-
- private static final int DOCK_AREA_ALPHA = 80;
- public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
- null, null, null);
- public static final DockState LEFT = new DockState(DOCKED_LEFT,
- DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
- new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
- new RectF(0, 0, 0.5f, 1));
- public static final DockState TOP = new DockState(DOCKED_TOP,
- DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
- new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
- new RectF(0, 0, 1, 0.5f));
- public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
- DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
- new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
- new RectF(0.5f, 0, 1, 1));
- public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
- DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
- new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
- new RectF(0, 0.5f, 1, 1));
-
- @Override
- public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
- boolean isCurrentTarget) {
- if (isCurrentTarget) {
- getMappedRect(expandedTouchDockArea, width, height, mTmpRect);
- return mTmpRect.contains(x, y);
- } else {
- getMappedRect(touchArea, width, height, mTmpRect);
- updateBoundsWithSystemInsets(mTmpRect, insets);
- return mTmpRect.contains(x, y);
- }
- }
-
- // Represents the view state of this dock state
- public static class ViewState {
- private static final IntProperty<ViewState> HINT_ALPHA =
- new IntProperty<ViewState>("drawableAlpha") {
- @Override
- public void setValue(ViewState object, int alpha) {
- object.mHintTextAlpha = alpha;
- object.dockAreaOverlay.invalidateSelf();
- }
-
- @Override
- public Integer get(ViewState object) {
- return object.mHintTextAlpha;
- }
- };
-
- public final int dockAreaAlpha;
- public final ColorDrawable dockAreaOverlay;
- public final int hintTextAlpha;
- public final int hintTextOrientation;
-
- private final int mHintTextResId;
- private String mHintText;
- private Paint mHintTextPaint;
- private Point mHintTextBounds = new Point();
- private int mHintTextAlpha = 255;
- private AnimatorSet mDockAreaOverlayAnimator;
- private Rect mTmpRect = new Rect();
-
- private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
- int hintTextResId) {
- dockAreaAlpha = areaAlpha;
- dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled
- ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
- dockAreaOverlay.setAlpha(0);
- hintTextAlpha = hintAlpha;
- hintTextOrientation = hintOrientation;
- mHintTextResId = hintTextResId;
- mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mHintTextPaint.setColor(Color.WHITE);
- }
-
- /**
- * Updates the view state with the given context.
- */
- public void update(Context context) {
- Resources res = context.getResources();
- mHintText = context.getString(mHintTextResId);
- mHintTextPaint.setTextSize(res.getDimensionPixelSize(
- R.dimen.recents_drag_hint_text_size));
- mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect);
- mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height());
- }
-
- /**
- * Draws the current view state.
- */
- public void draw(Canvas canvas) {
- // Draw the overlay background
- if (dockAreaOverlay.getAlpha() > 0) {
- dockAreaOverlay.draw(canvas);
- }
-
- // Draw the hint text
- if (mHintTextAlpha > 0) {
- Rect bounds = dockAreaOverlay.getBounds();
- int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2;
- int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2;
- mHintTextPaint.setAlpha(mHintTextAlpha);
- if (hintTextOrientation == VERTICAL) {
- canvas.save();
- canvas.rotate(-90f, bounds.centerX(), bounds.centerY());
- }
- canvas.drawText(mHintText, x, y, mHintTextPaint);
- if (hintTextOrientation == VERTICAL) {
- canvas.restore();
- }
- }
- }
-
- /**
- * Creates a new bounds and alpha animation.
- */
- public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration,
- Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
- if (mDockAreaOverlayAnimator != null) {
- mDockAreaOverlayAnimator.cancel();
- }
-
- ObjectAnimator anim;
- ArrayList<Animator> animators = new ArrayList<>();
- if (dockAreaOverlay.getAlpha() != areaAlpha) {
- if (animateAlpha) {
- anim = ObjectAnimator.ofInt(dockAreaOverlay,
- Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha);
- anim.setDuration(duration);
- anim.setInterpolator(interpolator);
- animators.add(anim);
- } else {
- dockAreaOverlay.setAlpha(areaAlpha);
- }
- }
- if (mHintTextAlpha != hintAlpha) {
- if (animateAlpha) {
- anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha,
- hintAlpha);
- anim.setDuration(150);
- anim.setInterpolator(hintAlpha > mHintTextAlpha
- ? Interpolators.ALPHA_IN
- : Interpolators.ALPHA_OUT);
- animators.add(anim);
- } else {
- mHintTextAlpha = hintAlpha;
- dockAreaOverlay.invalidateSelf();
- }
- }
- if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
- if (animateBounds) {
- PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
- Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR,
- new Rect(dockAreaOverlay.getBounds()), bounds);
- anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop);
- anim.setDuration(duration);
- anim.setInterpolator(interpolator);
- animators.add(anim);
- } else {
- dockAreaOverlay.setBounds(bounds);
- }
- }
- if (!animators.isEmpty()) {
- mDockAreaOverlayAnimator = new AnimatorSet();
- mDockAreaOverlayAnimator.playTogether(animators);
- mDockAreaOverlayAnimator.start();
- }
- }
- }
-
- public final int dockSide;
- public final int createMode;
- public final ViewState viewState;
- private final RectF touchArea;
- private final RectF dockArea;
- private final RectF expandedTouchDockArea;
- private static final Rect mTmpRect = new Rect();
-
- /**
- * @param createMode used to pass to ActivityManager to dock the task
- * @param touchArea the area in which touch will initiate this dock state
- * @param dockArea the visible dock area
- * @param expandedTouchDockArea the area in which touch will continue to dock after entering
- * the initial touch area. This is also the new dock area to
- * draw.
- */
- DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha,
- @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea,
- RectF expandedTouchDockArea) {
- this.dockSide = dockSide;
- this.createMode = createMode;
- this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation,
- R.string.recents_drag_hint_message);
- this.dockArea = dockArea;
- this.touchArea = touchArea;
- this.expandedTouchDockArea = expandedTouchDockArea;
- }
-
- /**
- * Updates the dock state with the given context.
- */
- public void update(Context context) {
- viewState.update(context);
- }
-
- /**
- * Returns the docked task bounds with the given {@param width} and {@param height}.
- */
- public Rect getPreDockedBounds(int width, int height, Rect insets) {
- getMappedRect(dockArea, width, height, mTmpRect);
- return updateBoundsWithSystemInsets(mTmpRect, insets);
- }
-
- /**
- * Returns the expanded docked task bounds with the given {@param width} and
- * {@param height}.
- */
- public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
- Resources res) {
- // Calculate the docked task bounds
- boolean isHorizontalDivision =
- res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
- int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
- insets, width, height, dividerSize);
- Rect newWindowBounds = new Rect();
- DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
- width, height, dividerSize);
- return newWindowBounds;
- }
-
- /**
- * Returns the task stack bounds with the given {@param width} and
- * {@param height}.
- */
- public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height,
- int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm,
- Resources res, Rect windowRectOut) {
- // Calculate the inverse docked task bounds
- boolean isHorizontalDivision =
- res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
- int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
- insets, width, height, dividerSize);
- DockedDividerUtils.calculateBoundsForPosition(position,
- DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
- dividerSize);
-
- // Calculate the task stack bounds from the new window bounds
- Rect taskStackBounds = new Rect();
- // If the task stack bounds is specifically under the dock area, then ignore the top
- // inset
- int top = dockArea.bottom < 1f
- ? 0
- : insets.top;
- // For now, ignore the left insets since we always dock on the left and show Recents
- // on the right
- layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right,
- taskStackBounds);
- return taskStackBounds;
- }
-
- /**
- * Returns the expanded bounds in certain dock sides such that the bounds account for the
- * system insets (namely the vertical nav bar). This call modifies and returns the given
- * {@param bounds}.
- */
- private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
- if (dockSide == DOCKED_LEFT) {
- bounds.right += insets.left;
- } else if (dockSide == DOCKED_RIGHT) {
- bounds.left -= insets.right;
- }
- return bounds;
- }
-
- /**
- * Returns the mapped rect to the given dimensions.
- */
- private void getMappedRect(RectF bounds, int width, int height, Rect out) {
- out.set((int) (bounds.left * width), (int) (bounds.top * height),
- (int) (bounds.right * width), (int) (bounds.bottom * height));
- }
- }
-
- // A comparator that sorts tasks by their freeform state
- private Comparator<Task> FREEFORM_COMPARATOR = new Comparator<Task>() {
- @Override
- public int compare(Task o1, Task o2) {
- if (o1.isFreeformTask() && !o2.isFreeformTask()) {
- return 1;
- } else if (o2.isFreeformTask() && !o1.isFreeformTask()) {
- return -1;
- }
- return Long.compare(o1.temporarySortIndexInStack, o2.temporarySortIndexInStack);
- }
- };
-
-
- // The task offset to apply to a task id as a group affiliation
- static final int IndividualTaskIdOffset = 1 << 16;
-
- ArrayList<Task> mRawTaskList = new ArrayList<>();
- FilteredTaskList mStackTaskList = new FilteredTaskList();
- TaskStackCallbacks mCb;
-
- ArrayList<TaskGrouping> mGroups = new ArrayList<>();
- ArrayMap<Integer, TaskGrouping> mAffinitiesGroups = new ArrayMap<>();
-
- public TaskStack() {
- // Ensure that we only show non-docked tasks
- mStackTaskList.setFilter(new TaskFilter() {
- @Override
- public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {
- if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
- if (t.isAffiliatedTask()) {
- // If this task is affiliated with another parent in the stack, then the
- // historical state of this task depends on the state of the parent task
- Task parentTask = taskIdMap.get(t.affiliationTaskId);
- if (parentTask != null) {
- t = parentTask;
- }
- }
- }
- return t.isStackTask;
- }
- });
- }
-
- /** Sets the callbacks for this task stack. */
- public void setCallbacks(TaskStackCallbacks cb) {
- mCb = cb;
- }
-
- /** 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() && (windowingMode == WINDOWING_MODE_FREEFORM)) {
- // Insert freeform tasks at the front
- 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--) {
- if (!taskList.get(i).isFreeformTask()) {
- insertIndex = i + 1;
- break;
- }
- }
- mStackTaskList.setTaskWindowingMode(task, insertIndex, windowingMode);
- }
- }
-
- /** Does the actual work associated with removing the task. */
- void removeTaskImpl(FilteredTaskList taskList, Task t) {
- // Remove the task from the list
- taskList.remove(t);
- // Remove it from the group as well, and if it is empty, remove the group
- TaskGrouping group = t.group;
- if (group != null) {
- group.removeTask(t);
- if (group.getTaskCount() == 0) {
- removeGroup(group);
- }
- }
- }
-
- /**
- * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
- * how they should update themselves.
- */
- public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
- removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */);
- }
-
- /**
- * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
- * how they should update themselves.
- */
- public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture,
- boolean dismissRecentsIfAllRemoved) {
- if (mStackTaskList.contains(t)) {
- removeTaskImpl(mStackTaskList, t);
- Task newFrontMostTask = getStackFrontMostTask(false /* includeFreeform */);
- if (mCb != null) {
- // Notify that a task has been removed
- mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
- fromDockGesture, dismissRecentsIfAllRemoved);
- }
- }
- mRawTaskList.remove(t);
- }
-
- /**
- * Removes all tasks from the stack.
- */
- public void removeAllTasks(boolean notifyStackChanges) {
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- for (int i = tasks.size() - 1; i >= 0; i--) {
- Task t = tasks.get(i);
- removeTaskImpl(mStackTaskList, t);
- mRawTaskList.remove(t);
- }
- if (mCb != null && notifyStackChanges) {
- // Notify that all tasks have been removed
- mCb.onStackTasksRemoved(this);
- }
- }
-
-
- /**
- * @see #setTasks(Context, List, boolean, boolean)
- */
- public void setTasks(Context context, TaskStack stack, boolean notifyStackChanges) {
- setTasks(context, stack.mRawTaskList, notifyStackChanges);
- }
-
- /**
- * Sets a few tasks in one go, without calling any callbacks.
- *
- * @param tasks the new set of tasks to replace the current set.
- * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
- */
- public void setTasks(Context context, List<Task> tasks, boolean notifyStackChanges) {
- // Compute a has set for each of the tasks
- ArrayMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
- ArrayMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
- ArrayList<Task> addedTasks = new ArrayList<>();
- ArrayList<Task> removedTasks = new ArrayList<>();
- ArrayList<Task> allTasks = new ArrayList<>();
-
- // Disable notifications if there are no callbacks
- if (mCb == null) {
- notifyStackChanges = false;
- }
-
- // Remove any tasks that no longer exist
- int taskCount = mRawTaskList.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- Task task = mRawTaskList.get(i);
- if (!newTasksMap.containsKey(task.key)) {
- if (notifyStackChanges) {
- removedTasks.add(task);
- }
- }
- task.setGroup(null);
- }
-
- // Add any new tasks
- taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task newTask = tasks.get(i);
- Task currentTask = currentTasksMap.get(newTask.key);
- if (currentTask == null && notifyStackChanges) {
- addedTasks.add(newTask);
- } else if (currentTask != null) {
- // The current task has bound callbacks, so just copy the data from the new task
- // state and add it back into the list
- currentTask.copyFrom(newTask);
- newTask = currentTask;
- }
- allTasks.add(newTask);
- }
-
- // Sort all the tasks to ensure they are ordered correctly
- for (int i = allTasks.size() - 1; i >= 0; i--) {
- allTasks.get(i).temporarySortIndexInStack = i;
- }
- Collections.sort(allTasks, FREEFORM_COMPARATOR);
-
- mStackTaskList.set(allTasks);
- mRawTaskList = allTasks;
-
- // Update the affiliated groupings
- createAffiliatedGroupings(context);
-
- // Only callback for the removed tasks after the stack has updated
- int removedTaskCount = removedTasks.size();
- Task newFrontMostTask = getStackFrontMostTask(false);
- for (int i = 0; i < removedTaskCount; i++) {
- mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
- AnimationProps.IMMEDIATE, false /* fromDockGesture */,
- true /* dismissRecentsIfAllRemoved */);
- }
-
- // Only callback for the newly added tasks after this stack has been updated
- int addedTaskCount = addedTasks.size();
- for (int i = 0; i < addedTaskCount; i++) {
- mCb.onStackTaskAdded(this, addedTasks.get(i));
- }
-
- // Notify that the task stack has been updated
- if (notifyStackChanges) {
- mCb.onStackTasksUpdated(this);
- }
- }
-
- /**
- * Gets the front-most task in the stack.
- */
- public Task getStackFrontMostTask(boolean includeFreeformTasks) {
- ArrayList<Task> stackTasks = mStackTaskList.getTasks();
- if (stackTasks.isEmpty()) {
- return null;
- }
- for (int i = stackTasks.size() - 1; i >= 0; i--) {
- Task task = stackTasks.get(i);
- if (!task.isFreeformTask() || includeFreeformTasks) {
- return task;
- }
- }
- return null;
- }
-
- /** Gets the task keys */
- public ArrayList<Task.TaskKey> getTaskKeys() {
- ArrayList<Task.TaskKey> taskKeys = new ArrayList<>();
- ArrayList<Task> tasks = computeAllTasksList();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- taskKeys.add(task.key);
- }
- return taskKeys;
- }
-
- /**
- * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
- */
- public ArrayList<Task> getStackTasks() {
- return mStackTaskList.getTasks();
- }
-
- /**
- * Returns the set of "freeform" tasks in the stack.
- */
- public ArrayList<Task> getFreeformTasks() {
- ArrayList<Task> freeformTasks = new ArrayList<>();
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- if (task.isFreeformTask()) {
- freeformTasks.add(task);
- }
- }
- return freeformTasks;
- }
-
- /**
- * Computes a set of all the active and historical tasks.
- */
- public ArrayList<Task> computeAllTasksList() {
- ArrayList<Task> tasks = new ArrayList<>();
- tasks.addAll(mStackTaskList.getTasks());
- return tasks;
- }
-
- /**
- * Returns the number of stack and freeform tasks.
- */
- public int getTaskCount() {
- return mStackTaskList.size();
- }
-
- /**
- * Returns the number of stack tasks.
- */
- public int getStackTaskCount() {
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int stackCount = 0;
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- if (!task.isFreeformTask()) {
- stackCount++;
- }
- }
- return stackCount;
- }
-
- /**
- * Returns the number of freeform tasks.
- */
- public int getFreeformTaskCount() {
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int freeformCount = 0;
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- if (task.isFreeformTask()) {
- freeformCount++;
- }
- }
- return freeformCount;
- }
-
- /**
- * Returns the task in stack tasks which is the launch target.
- */
- public Task getLaunchTarget() {
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- if (task.isLaunchTarget) {
- return task;
- }
- }
- return null;
- }
-
- /**
- * Returns whether the next launch target should actually be the PiP task.
- */
- public boolean isNextLaunchTargetPip(long lastPipTime) {
- Task launchTarget = getLaunchTarget();
- Task nextLaunchTarget = getNextLaunchTargetRaw();
- if (nextLaunchTarget != null && lastPipTime > 0) {
- // If the PiP time is more recent than the next launch target, then launch the PiP task
- return lastPipTime > nextLaunchTarget.key.lastActiveTime;
- } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) {
- // Otherwise, if there is no next launch target, but there is a PiP, then launch
- // the PiP task
- return true;
- }
- return false;
- }
-
- /**
- * Returns the task in stack tasks which should be launched next if Recents are toggled
- * again, or null if there is no task to be launched. Callers should check
- * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the
- * stack.
- */
- public Task getNextLaunchTarget() {
- Task nextLaunchTarget = getNextLaunchTargetRaw();
- if (nextLaunchTarget != null) {
- return nextLaunchTarget;
- }
- return getStackTasks().get(getTaskCount() - 1);
- }
-
- private Task getNextLaunchTargetRaw() {
- int taskCount = getTaskCount();
- if (taskCount == 0) {
- return null;
- }
- int launchTaskIndex = indexOfStackTask(getLaunchTarget());
- if (launchTaskIndex != -1 && launchTaskIndex > 0) {
- return getStackTasks().get(launchTaskIndex - 1);
- }
- return null;
- }
-
- /** Returns the index of this task in this current task stack */
- public int indexOfStackTask(Task t) {
- return mStackTaskList.indexOf(t);
- }
-
- /** Finds the task with the specified task id. */
- public Task findTaskWithId(int taskId) {
- ArrayList<Task> tasks = computeAllTasksList();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- if (task.key.id == taskId) {
- return task;
- }
- }
- return null;
- }
-
- /******** Grouping ********/
-
- /** Adds a group to the set */
- public void addGroup(TaskGrouping group) {
- mGroups.add(group);
- mAffinitiesGroups.put(group.affiliation, group);
- }
-
- public void removeGroup(TaskGrouping group) {
- mGroups.remove(group);
- mAffinitiesGroups.remove(group.affiliation);
- }
-
- /** Returns the group with the specified affiliation. */
- public TaskGrouping getGroupWithAffiliation(int affiliation) {
- return mAffinitiesGroups.get(affiliation);
- }
-
- /**
- * Temporary: This method will simulate affiliation groups
- */
- void createAffiliatedGroupings(Context context) {
- mGroups.clear();
- mAffinitiesGroups.clear();
-
- if (RecentsDebugFlags.Static.EnableMockTaskGroups) {
- ArrayMap<Task.TaskKey, Task> taskMap = new ArrayMap<>();
- // Sort all tasks by increasing firstActiveTime of the task
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- Collections.sort(tasks, new Comparator<Task>() {
- @Override
- public int compare(Task task, Task task2) {
- return Long.compare(task.key.firstActiveTime, task2.key.firstActiveTime);
- }
- });
- // Create groups when sequential packages are the same
- NamedCounter counter = new NamedCounter("task-group", "");
- int taskCount = tasks.size();
- String prevPackage = "";
- int prevAffiliation = -1;
- Random r = new Random();
- int groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount;
- for (int i = 0; i < taskCount; i++) {
- Task t = tasks.get(i);
- String packageName = t.key.getComponent().getPackageName();
- packageName = "pkg";
- TaskGrouping group;
- if (packageName.equals(prevPackage) && groupCountDown > 0) {
- group = getGroupWithAffiliation(prevAffiliation);
- groupCountDown--;
- } else {
- int affiliation = IndividualTaskIdOffset + t.key.id;
- group = new TaskGrouping(affiliation);
- addGroup(group);
- prevAffiliation = affiliation;
- prevPackage = packageName;
- groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount;
- }
- group.addTask(t);
- taskMap.put(t.key, t);
- }
- // Sort groups by increasing latestActiveTime of the group
- Collections.sort(mGroups, new Comparator<TaskGrouping>() {
- @Override
- public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) {
- return Long.compare(taskGrouping.latestActiveTimeInGroup,
- taskGrouping2.latestActiveTimeInGroup);
- }
- });
- // Sort group tasks by increasing firstActiveTime of the task, and also build a new list
- // of tasks
- int taskIndex = 0;
- int groupCount = mGroups.size();
- for (int i = 0; i < groupCount; i++) {
- TaskGrouping group = mGroups.get(i);
- Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() {
- @Override
- public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) {
- return Long.compare(taskKey.firstActiveTime, taskKey2.firstActiveTime);
- }
- });
- ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys;
- int groupTaskCount = groupTasks.size();
- for (int j = 0; j < groupTaskCount; j++) {
- tasks.set(taskIndex, taskMap.get(groupTasks.get(j)));
- taskIndex++;
- }
- }
- mStackTaskList.set(tasks);
- } else {
- // Create the task groups
- ArrayMap<Task.TaskKey, Task> tasksMap = new ArrayMap<>();
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task t = tasks.get(i);
- TaskGrouping group;
- if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
- int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId :
- IndividualTaskIdOffset + t.key.id;
- if (mAffinitiesGroups.containsKey(affiliation)) {
- group = getGroupWithAffiliation(affiliation);
- } else {
- group = new TaskGrouping(affiliation);
- addGroup(group);
- }
- } else {
- group = new TaskGrouping(t.key.id);
- addGroup(group);
- }
- group.addTask(t);
- tasksMap.put(t.key, t);
- }
- // Update the task colors for each of the groups
- float minAlpha = context.getResources().getFloat(
- R.dimen.recents_task_affiliation_color_min_alpha_percentage);
- int taskGroupCount = mGroups.size();
- for (int i = 0; i < taskGroupCount; i++) {
- TaskGrouping group = mGroups.get(i);
- taskCount = group.getTaskCount();
- // Ignore the groups that only have one task
- if (taskCount <= 1) continue;
- // Calculate the group color distribution
- int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).affiliationColor;
- float alphaStep = (1f - minAlpha) / taskCount;
- float alpha = 1f;
- for (int j = 0; j < taskCount; j++) {
- Task t = tasksMap.get(group.mTaskKeys.get(j));
- t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE,
- alpha);
- alpha -= alphaStep;
- }
- }
- }
- }
-
- /**
- * Computes the components of tasks in this stack that have been removed as a result of a change
- * in the specified package.
- */
- public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
- // Identify all the tasks that should be removed as a result of the package being removed.
- // Using a set to ensure that we callback once per unique component.
- SystemServicesProxy ssp = Recents.getSystemServices();
- ArraySet<ComponentName> existingComponents = new ArraySet<>();
- ArraySet<ComponentName> removedComponents = new ArraySet<>();
- ArrayList<Task.TaskKey> taskKeys = getTaskKeys();
- int taskKeyCount = taskKeys.size();
- for (int i = 0; i < taskKeyCount; i++) {
- Task.TaskKey t = taskKeys.get(i);
-
- // Skip if this doesn't apply to the current user
- if (t.userId != userId) continue;
-
- ComponentName cn = t.getComponent();
- if (cn.getPackageName().equals(packageName)) {
- if (existingComponents.contains(cn)) {
- // If we know that the component still exists in the package, then skip
- continue;
- }
- if (ssp.getActivityInfo(cn, userId) != null) {
- existingComponents.add(cn);
- } else {
- removedComponents.add(cn);
- }
- }
- }
- return removedComponents;
- }
-
- @Override
- public String toString() {
- String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- str += " " + tasks.get(i).toString() + "\n";
- }
- return str;
- }
-
- /**
- * Given a list of tasks, returns a map of each task's key to the task.
- */
- private ArrayMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
- ArrayMap<Task.TaskKey, Task> map = new ArrayMap<>(tasks.size());
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- map.put(task.key, task);
- }
- return map;
- }
-
- public void dump(String prefix, PrintWriter writer) {
- String innerPrefix = prefix + " ";
-
- writer.print(prefix); writer.print(TAG);
- writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
- writer.println();
- ArrayList<Task> tasks = mStackTaskList.getTasks();
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- tasks.get(i).dump(innerPrefix, writer);
- }
- }
-}
diff --git a/com/android/systemui/recents/views/AnimateableViewBounds.java b/com/android/systemui/recents/views/AnimateableViewBounds.java
index 7998ecb4..b598ec6f 100644
--- a/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -22,7 +22,7 @@ import android.view.View;
import android.view.ViewDebug;
import android.view.ViewOutlineProvider;
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.Utilities;
/* An outline provider that has a clip and outline that can be animated. */
public class AnimateableViewBounds extends ViewOutlineProvider {
diff --git a/com/android/systemui/recents/views/DockState.java b/com/android/systemui/recents/views/DockState.java
new file mode 100644
index 00000000..59f28680
--- /dev/null
+++ b/com/android/systemui/recents/views/DockState.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+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.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
+import android.util.IntProperty;
+import android.view.animation.Interpolator;
+
+import com.android.internal.policy.DockedDividerUtils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.shared.recents.utilities.Utilities;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * The various possible dock states when dragging and dropping a task.
+ */
+public class DockState implements DropTarget {
+
+ public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
+ public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
+
+ // The rotation to apply to the hint text
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({HORIZONTAL, VERTICAL})
+ public @interface TextOrientation {}
+ private static final int HORIZONTAL = 0;
+ private static final int VERTICAL = 1;
+
+ private static final int DOCK_AREA_ALPHA = 80;
+ public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
+ null, null, null);
+ public static final DockState LEFT = new DockState(DOCKED_LEFT,
+ DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
+ new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
+ new RectF(0, 0, 0.5f, 1));
+ public static final DockState TOP = new DockState(DOCKED_TOP,
+ DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+ new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
+ new RectF(0, 0, 1, 0.5f));
+ public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
+ DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
+ new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
+ new RectF(0.5f, 0, 1, 1));
+ public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
+ DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+ new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
+ new RectF(0, 0.5f, 1, 1));
+
+ @Override
+ public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
+ boolean isCurrentTarget) {
+ if (isCurrentTarget) {
+ getMappedRect(expandedTouchDockArea, width, height, mTmpRect);
+ return mTmpRect.contains(x, y);
+ } else {
+ getMappedRect(touchArea, width, height, mTmpRect);
+ updateBoundsWithSystemInsets(mTmpRect, insets);
+ return mTmpRect.contains(x, y);
+ }
+ }
+
+ // Represents the view state of this dock state
+ public static class ViewState {
+ private static final IntProperty<ViewState> HINT_ALPHA =
+ new IntProperty<ViewState>("drawableAlpha") {
+ @Override
+ public void setValue(ViewState object, int alpha) {
+ object.mHintTextAlpha = alpha;
+ object.dockAreaOverlay.invalidateSelf();
+ }
+
+ @Override
+ public Integer get(ViewState object) {
+ return object.mHintTextAlpha;
+ }
+ };
+
+ public final int dockAreaAlpha;
+ public final ColorDrawable dockAreaOverlay;
+ public final int hintTextAlpha;
+ public final int hintTextOrientation;
+
+ private final int mHintTextResId;
+ private String mHintText;
+ private Paint mHintTextPaint;
+ private Point mHintTextBounds = new Point();
+ private int mHintTextAlpha = 255;
+ private AnimatorSet mDockAreaOverlayAnimator;
+ private Rect mTmpRect = new Rect();
+
+ private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
+ int hintTextResId) {
+ dockAreaAlpha = areaAlpha;
+ dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled
+ ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
+ dockAreaOverlay.setAlpha(0);
+ hintTextAlpha = hintAlpha;
+ hintTextOrientation = hintOrientation;
+ mHintTextResId = hintTextResId;
+ mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mHintTextPaint.setColor(Color.WHITE);
+ }
+
+ /**
+ * Updates the view state with the given context.
+ */
+ public void update(Context context) {
+ Resources res = context.getResources();
+ mHintText = context.getString(mHintTextResId);
+ mHintTextPaint.setTextSize(res.getDimensionPixelSize(
+ R.dimen.recents_drag_hint_text_size));
+ mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect);
+ mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height());
+ }
+
+ /**
+ * Draws the current view state.
+ */
+ public void draw(Canvas canvas) {
+ // Draw the overlay background
+ if (dockAreaOverlay.getAlpha() > 0) {
+ dockAreaOverlay.draw(canvas);
+ }
+
+ // Draw the hint text
+ if (mHintTextAlpha > 0) {
+ Rect bounds = dockAreaOverlay.getBounds();
+ int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2;
+ int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2;
+ mHintTextPaint.setAlpha(mHintTextAlpha);
+ if (hintTextOrientation == VERTICAL) {
+ canvas.save();
+ canvas.rotate(-90f, bounds.centerX(), bounds.centerY());
+ }
+ canvas.drawText(mHintText, x, y, mHintTextPaint);
+ if (hintTextOrientation == VERTICAL) {
+ canvas.restore();
+ }
+ }
+ }
+
+ /**
+ * Creates a new bounds and alpha animation.
+ */
+ public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration,
+ Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
+ if (mDockAreaOverlayAnimator != null) {
+ mDockAreaOverlayAnimator.cancel();
+ }
+
+ ObjectAnimator anim;
+ ArrayList<Animator> animators = new ArrayList<>();
+ if (dockAreaOverlay.getAlpha() != areaAlpha) {
+ if (animateAlpha) {
+ anim = ObjectAnimator.ofInt(dockAreaOverlay,
+ Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha);
+ anim.setDuration(duration);
+ anim.setInterpolator(interpolator);
+ animators.add(anim);
+ } else {
+ dockAreaOverlay.setAlpha(areaAlpha);
+ }
+ }
+ if (mHintTextAlpha != hintAlpha) {
+ if (animateAlpha) {
+ anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha,
+ hintAlpha);
+ anim.setDuration(150);
+ anim.setInterpolator(hintAlpha > mHintTextAlpha
+ ? Interpolators.ALPHA_IN
+ : Interpolators.ALPHA_OUT);
+ animators.add(anim);
+ } else {
+ mHintTextAlpha = hintAlpha;
+ dockAreaOverlay.invalidateSelf();
+ }
+ }
+ if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
+ if (animateBounds) {
+ PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
+ Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR,
+ new Rect(dockAreaOverlay.getBounds()), bounds);
+ anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop);
+ anim.setDuration(duration);
+ anim.setInterpolator(interpolator);
+ animators.add(anim);
+ } else {
+ dockAreaOverlay.setBounds(bounds);
+ }
+ }
+ if (!animators.isEmpty()) {
+ mDockAreaOverlayAnimator = new AnimatorSet();
+ mDockAreaOverlayAnimator.playTogether(animators);
+ mDockAreaOverlayAnimator.start();
+ }
+ }
+ }
+
+ public final int dockSide;
+ public final int createMode;
+ public final ViewState viewState;
+ private final RectF touchArea;
+ private final RectF dockArea;
+ private final RectF expandedTouchDockArea;
+ private static final Rect mTmpRect = new Rect();
+
+ /**
+ * @param createMode used to pass to ActivityManager to dock the task
+ * @param touchArea the area in which touch will initiate this dock state
+ * @param dockArea the visible dock area
+ * @param expandedTouchDockArea the area in which touch will continue to dock after entering
+ * the initial touch area. This is also the new dock area to
+ * draw.
+ */
+ DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha,
+ @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea,
+ RectF expandedTouchDockArea) {
+ this.dockSide = dockSide;
+ this.createMode = createMode;
+ this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation,
+ R.string.recents_drag_hint_message);
+ this.dockArea = dockArea;
+ this.touchArea = touchArea;
+ this.expandedTouchDockArea = expandedTouchDockArea;
+ }
+
+ /**
+ * Updates the dock state with the given context.
+ */
+ public void update(Context context) {
+ viewState.update(context);
+ }
+
+ /**
+ * Returns the docked task bounds with the given {@param width} and {@param height}.
+ */
+ public Rect getPreDockedBounds(int width, int height, Rect insets) {
+ getMappedRect(dockArea, width, height, mTmpRect);
+ return updateBoundsWithSystemInsets(mTmpRect, insets);
+ }
+
+ /**
+ * Returns the expanded docked task bounds with the given {@param width} and
+ * {@param height}.
+ */
+ public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
+ Resources res) {
+ // Calculate the docked task bounds
+ boolean isHorizontalDivision =
+ res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+ int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+ insets, width, height, dividerSize);
+ Rect newWindowBounds = new Rect();
+ DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
+ width, height, dividerSize);
+ return newWindowBounds;
+ }
+
+ /**
+ * Returns the task stack bounds with the given {@param width} and
+ * {@param height}.
+ */
+ public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height,
+ int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm,
+ Resources res, Rect windowRectOut) {
+ // Calculate the inverse docked task bounds
+ boolean isHorizontalDivision =
+ res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+ int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+ insets, width, height, dividerSize);
+ DockedDividerUtils.calculateBoundsForPosition(position,
+ DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
+ dividerSize);
+
+ // Calculate the task stack bounds from the new window bounds
+ Rect taskStackBounds = new Rect();
+ // If the task stack bounds is specifically under the dock area, then ignore the top
+ // inset
+ int top = dockArea.bottom < 1f
+ ? 0
+ : insets.top;
+ // For now, ignore the left insets since we always dock on the left and show Recents
+ // on the right
+ layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right,
+ taskStackBounds);
+ return taskStackBounds;
+ }
+
+ /**
+ * Returns the expanded bounds in certain dock sides such that the bounds account for the
+ * system insets (namely the vertical nav bar). This call modifies and returns the given
+ * {@param bounds}.
+ */
+ private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
+ if (dockSide == DOCKED_LEFT) {
+ bounds.right += insets.left;
+ } else if (dockSide == DOCKED_RIGHT) {
+ bounds.left -= insets.right;
+ }
+ return bounds;
+ }
+
+ /**
+ * Returns the mapped rect to the given dimensions.
+ */
+ private void getMappedRect(RectF bounds, int width, int height, Rect out) {
+ out.set((int) (bounds.left * width), (int) (bounds.top * height),
+ (int) (bounds.right * width), (int) (bounds.bottom * height));
+ }
+} \ No newline at end of file
diff --git a/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
deleted file mode 100644
index 035c058c..00000000
--- a/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ /dev/null
@@ -1,170 +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.systemui.recents.views;
-
-import android.content.Context;
-import android.graphics.RectF;
-import android.util.ArrayMap;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.model.Task;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * The layout logic for the contents of the freeform workspace.
- */
-public class FreeformWorkspaceLayoutAlgorithm {
-
- // Optimization, allows for quick lookup of task -> rect
- private ArrayMap<Task.TaskKey, RectF> mTaskRectMap = new ArrayMap<>();
-
- private int mTaskPadding;
-
- public FreeformWorkspaceLayoutAlgorithm(Context context) {
- reloadOnConfigurationChange(context);
- }
-
- /**
- * Reloads the layout for the current configuration.
- */
- public void reloadOnConfigurationChange(Context context) {
- // This is applied to the edges of each task
- mTaskPadding = context.getResources().getDimensionPixelSize(
- R.dimen.recents_freeform_layout_task_padding) / 2;
- }
-
- /**
- * Updates the layout for each of the freeform workspace tasks. This is called after the stack
- * layout is updated.
- */
- public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
- Collections.reverse(freeformTasks);
- mTaskRectMap.clear();
-
- int numFreeformTasks = stackLayout.mNumFreeformTasks;
- if (!freeformTasks.isEmpty()) {
-
- // Normalize the widths so that we can calculate the best layout below
- int workspaceWidth = stackLayout.mFreeformRect.width();
- int workspaceHeight = stackLayout.mFreeformRect.height();
- float normalizedWorkspaceWidth = (float) workspaceWidth / workspaceHeight;
- float normalizedWorkspaceHeight = 1f;
- float[] normalizedTaskWidths = new float[numFreeformTasks];
- for (int i = 0; i < numFreeformTasks; i++) {
- Task task = freeformTasks.get(i);
- float rowTaskWidth;
- if (task.bounds != null) {
- rowTaskWidth = (float) task.bounds.width() / task.bounds.height();
- } else {
- // If this is a stack task that was dragged into the freeform workspace, then
- // the task will not yet have an associated bounds, so assume the full workspace
- // width for the time being
- rowTaskWidth = normalizedWorkspaceWidth;
- }
- // Bound the task width to the workspace width so that at the worst case, it will
- // fit its own row
- normalizedTaskWidths[i] = Math.min(rowTaskWidth, normalizedWorkspaceWidth);
- }
-
- // Determine the scale to best fit each of the tasks in the workspace
- float rowScale = 0.85f;
- float rowWidth = 0f;
- float maxRowWidth = 0f;
- int rowCount = 1;
- for (int i = 0; i < numFreeformTasks;) {
- float width = normalizedTaskWidths[i] * rowScale;
- if (rowWidth + width > normalizedWorkspaceWidth) {
- // That is too long for this row, create new row
- if ((rowCount + 1) * rowScale > normalizedWorkspaceHeight) {
- // The new row is too high, so we need to try fitting again. Update the
- // scale to be the smaller of the scale needed to fit the task in the
- // previous row, or the scale needed to fit the new row
- rowScale = Math.min(normalizedWorkspaceWidth / (rowWidth + width),
- normalizedWorkspaceHeight / (rowCount + 1));
- rowCount = 1;
- rowWidth = 0;
- i = 0;
- } else {
- // The new row fits, so continue
- rowWidth = width;
- rowCount++;
- i++;
- }
- } else {
- // Task is OK in this row
- rowWidth += width;
- i++;
- }
- maxRowWidth = Math.max(rowWidth, maxRowWidth);
- }
-
- // Normalize each of the actual rects to that scale
- float defaultRowLeft = ((1f - (maxRowWidth / normalizedWorkspaceWidth)) *
- workspaceWidth) / 2f;
- float rowLeft = defaultRowLeft;
- float rowTop = ((1f - (rowScale * rowCount)) * workspaceHeight) / 2f;
- float rowHeight = rowScale * workspaceHeight;
- for (int i = 0; i < numFreeformTasks; i++) {
- Task task = freeformTasks.get(i);
- float width = rowHeight * normalizedTaskWidths[i];
- if (rowLeft + width > workspaceWidth) {
- // This goes on the next line
- rowTop += rowHeight;
- rowLeft = defaultRowLeft;
- }
- RectF rect = new RectF(rowLeft, rowTop, rowLeft + width, rowTop + rowHeight);
- rect.inset(mTaskPadding, mTaskPadding);
- rowLeft += width;
- mTaskRectMap.put(task.key, rect);
- }
- }
- }
-
- /**
- * Returns whether the transform is available for the given task.
- */
- public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) {
- if (stackLayout.mNumFreeformTasks == 0 || task == null) {
- return false;
- }
- return mTaskRectMap.containsKey(task.key);
- }
-
- /**
- * Returns the transform for the given task. Any rect returned will be offset by the actual
- * transform for the freeform workspace.
- */
- public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
- TaskStackLayoutAlgorithm stackLayout) {
- if (mTaskRectMap.containsKey(task.key)) {
- final RectF ffRect = mTaskRectMap.get(task.key);
-
- transformOut.scale = 1f;
- transformOut.alpha = 1f;
- transformOut.translationZ = stackLayout.mMaxTranslationZ;
- transformOut.dimAlpha = 0f;
- transformOut.viewOutlineAlpha = TaskStackLayoutAlgorithm.OUTLINE_ALPHA_MAX_VALUE;
- transformOut.rect.set(ffRect);
- transformOut.rect.offset(stackLayout.mFreeformRect.left, stackLayout.mFreeformRect.top);
- transformOut.visible = true;
- return transformOut;
- }
- return null;
- }
-}
diff --git a/com/android/systemui/recents/views/RecentsTransitionHelper.java b/com/android/systemui/recents/views/RecentsTransitionHelper.java
index ee05d81c..25c2fc97 100644
--- a/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -19,7 +19,6 @@ package com.android.systemui.recents.views;
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;
@@ -31,8 +30,6 @@ import android.app.ActivityOptions;
import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.GraphicBuffer;
import android.graphics.Rect;
import android.os.Bundle;
@@ -59,8 +56,8 @@ import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
@@ -188,20 +185,9 @@ public class RecentsTransitionHelper {
} else {
LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
screenPinningRequested);
- if (task.group != null && !task.group.isFrontMostTask(task)) {
- launchStartedEvent.addPostAnimationCallback(new Runnable() {
- @Override
- public void run() {
- startTaskActivity(stack, task, taskView, opts, transitionFuture,
- windowingMode, activityType);
- }
- });
- EventBus.getDefault().send(launchStartedEvent);
- } else {
- EventBus.getDefault().send(launchStartedEvent);
- startTaskActivity(stack, task, taskView, opts, transitionFuture,
- windowingMode, activityType);
- }
+ EventBus.getDefault().send(launchStartedEvent);
+ startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
+ activityType);
}
Recents.getSystemServices().sendCloseSystemWindows(
StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
@@ -329,7 +315,6 @@ public class RecentsTransitionHelper {
// If this is a full screen stack, the transition will be towards the single, full screen
// task. We only need the transition spec for this task.
- List<AppTransitionAnimationSpec> specs = new ArrayList<>();
// TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
// check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
@@ -338,6 +323,7 @@ public class RecentsTransitionHelper {
|| windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
|| activityType == ACTIVITY_TYPE_ASSISTANT
|| windowingMode == WINDOWING_MODE_UNDEFINED) {
+ List<AppTransitionAnimationSpec> specs = new ArrayList<>();
if (taskView == null) {
specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
} else {
@@ -351,34 +337,7 @@ public class RecentsTransitionHelper {
}
return specs;
}
-
- // Otherwise, for freeform tasks, create a new animation spec for each task we have to
- // launch
- TaskStack stack = stackView.getStack();
- ArrayList<Task> tasks = stack.getStackTasks();
- int taskCount = tasks.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- Task t = tasks.get(i);
- 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
- // never happen)
- specs.add(composeOffscreenAnimationSpec(t, offscreenTaskRect));
- } else {
- mTmpTransform.fillIn(taskView);
- stackLayout.transformToScreenCoordinates(mTmpTransform,
- null /* windowOverrideRect */);
- AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, tv,
- mTmpTransform, true /* addHeaderBitmap */);
- if (spec != null) {
- specs.add(spec);
- }
- }
- }
- }
-
- return specs;
+ return Collections.emptyList();
}
/**
@@ -462,7 +421,7 @@ public class RecentsTransitionHelper {
// force the task thumbnail to full stackView height immediately causing the transition
// jarring.
if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
- stackView.getStack().getStackFrontMostTask(false /* includeFreeformTasks */)) {
+ stackView.getStack().getStackFrontMostTask()) {
taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
}
return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java
index c7edb9ae..5f12a04b 100644
--- a/com/android/systemui/recents/views/RecentsView.java
+++ b/com/android/systemui/recents/views/RecentsView.java
@@ -16,10 +16,6 @@
package com.android.systemui.recents.views;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.ActivityOptions.OnAnimationStartedListener;
@@ -57,7 +53,6 @@ import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
@@ -78,9 +73,9 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
import com.android.systemui.stackdivider.WindowManagerProxy;
@@ -114,7 +109,6 @@ public class RecentsView extends FrameLayout {
private final int mStackButtonShadowColor;
private boolean mAwaitingFirstLayout = true;
- private boolean mLastTaskLaunchedWasFreeform;
@ViewDebug.ExportedProperty(category="recents")
Rect mSystemInsets = new Rect();
@@ -165,22 +159,20 @@ public class RecentsView extends FrameLayout {
mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
addView(mEmptyView);
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- if (mStackActionButton != null) {
- removeView(mStackActionButton);
- }
- mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration()
- .isLowRamDevice
- ? R.layout.recents_low_ram_stack_action_button
- : R.layout.recents_stack_action_button,
- this, false);
-
- mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
- mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
- mStackActionButton.getShadowDy());
- mStackButtonShadowColor = mStackActionButton.getShadowColor();
- addView(mStackActionButton);
+ if (mStackActionButton != null) {
+ removeView(mStackActionButton);
}
+ mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration()
+ .isLowRamDevice
+ ? R.layout.recents_low_ram_stack_action_button
+ : R.layout.recents_stack_action_button,
+ this, false);
+
+ mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
+ mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
+ mStackActionButton.getShadowDy());
+ mStackButtonShadowColor = mStackActionButton.getShadowColor();
+ addView(mStackActionButton);
reevaluateStyles();
}
@@ -232,7 +224,6 @@ public class RecentsView extends FrameLayout {
// Reset the state
mAwaitingFirstLayout = !isResumingFromVisible;
- mLastTaskLaunchedWasFreeform = false;
// Update the stack
mTaskStackView.onReload(isResumingFromVisible);
@@ -319,13 +310,6 @@ public class RecentsView extends FrameLayout {
}
}
- /**
- * Returns whether the last task launched was in the freeform stack or not.
- */
- public boolean isLastTaskLaunchedFreeform() {
- return mLastTaskLaunchedWasFreeform;
- }
-
/** Launches the focused task from the first stack if possible */
public boolean launchFocusedTask(int logEvent) {
if (mTaskStackView != null) {
@@ -371,9 +355,7 @@ public class RecentsView extends FrameLayout {
mEmptyView.setText(msgResId);
mEmptyView.setVisibility(View.VISIBLE);
mEmptyView.bringToFront();
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- mStackActionButton.bringToFront();
- }
+ mStackActionButton.bringToFront();
}
/**
@@ -383,9 +365,7 @@ public class RecentsView extends FrameLayout {
mEmptyView.setVisibility(View.INVISIBLE);
mTaskStackView.setVisibility(View.VISIBLE);
mTaskStackView.bringToFront();
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- mStackActionButton.bringToFront();
- }
+ mStackActionButton.bringToFront();
}
/**
@@ -433,13 +413,11 @@ public class RecentsView extends FrameLayout {
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
}
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- // Measure the stack action button within the constraints of the space above the stack
- Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
- measureChild(mStackActionButton,
- MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
- }
+ // Measure the stack action button within the constraints of the space above the stack
+ Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
+ measureChild(mStackActionButton,
+ MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
setMeasuredDimension(width, height);
}
@@ -473,13 +451,11 @@ public class RecentsView extends FrameLayout {
mBackgroundScrim.setBounds(left, top, right, bottom);
mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- // Layout the stack action button such that its drawable is start-aligned with the
- // stack, vertically centered in the available space above the stack
- Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
- mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
- buttonBounds.bottom);
- }
+ // Layout the stack action button such that its drawable is start-aligned with the
+ // stack, vertically centered in the available space above the stack
+ Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
+ mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
+ buttonBounds.bottom);
if (mAwaitingFirstLayout) {
mAwaitingFirstLayout = false;
@@ -521,7 +497,7 @@ public class RecentsView extends FrameLayout {
public void onDrawForeground(Canvas canvas) {
super.onDrawForeground(canvas);
- ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+ ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
for (int i = visDockStates.size() - 1; i >= 0; i--) {
visDockStates.get(i).viewState.draw(canvas);
}
@@ -529,7 +505,7 @@ public class RecentsView extends FrameLayout {
@Override
protected boolean verifyDrawable(Drawable who) {
- ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+ ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
for (int i = visDockStates.size() - 1; i >= 0; i--) {
Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
if (d == who) {
@@ -542,7 +518,6 @@ public class RecentsView extends FrameLayout {
/**** EventBus Events ****/
public final void onBusEvent(LaunchTaskEvent event) {
- mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
event.taskView, event.screenPinningRequested, event.targetWindowingMode,
event.targetActivityType);
@@ -553,10 +528,8 @@ public class RecentsView extends FrameLayout {
public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- // Hide the stack action button
- EventBus.getDefault().send(new HideStackActionButtonEvent());
- }
+ // Hide the stack action button
+ EventBus.getDefault().send(new HideStackActionButtonEvent());
animateBackgroundScrim(0f, taskViewExitToHomeDuration);
if (Recents.getConfiguration().isLowRamDevice) {
@@ -566,8 +539,8 @@ public class RecentsView extends FrameLayout {
public final void onBusEvent(DragStartEvent event) {
updateVisibleDockRegions(Recents.getConfiguration().getDockStatesForCurrentOrientation(),
- true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
- TaskStack.DockState.NONE.viewState.hintTextAlpha,
+ true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
+ DockState.NONE.viewState.hintTextAlpha,
true /* animateAlpha */, false /* animateBounds */);
// Temporarily hide the stack action button without changing visibility
@@ -581,15 +554,15 @@ public class RecentsView extends FrameLayout {
}
public final void onBusEvent(DragDropTargetChangedEvent event) {
- if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
+ if (event.dropTarget == null || !(event.dropTarget instanceof DockState)) {
updateVisibleDockRegions(
Recents.getConfiguration().getDockStatesForCurrentOrientation(),
- true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
- TaskStack.DockState.NONE.viewState.hintTextAlpha,
+ true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
+ DockState.NONE.viewState.hintTextAlpha,
true /* animateAlpha */, true /* animateBounds */);
} else {
- final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
- updateVisibleDockRegions(new TaskStack.DockState[] {dockState},
+ final DockState dockState = (DockState) event.dropTarget;
+ updateVisibleDockRegions(new DockState[] {dockState},
false /* isDefaultDockState */, -1, -1, true /* animateAlpha */,
true /* animateBounds */);
}
@@ -608,8 +581,8 @@ public class RecentsView extends FrameLayout {
public final void onBusEvent(final DragEndEvent event) {
// Handle the case where we drop onto a dock region
- if (event.dropTarget instanceof TaskStack.DockState) {
- final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
+ if (event.dropTarget instanceof DockState) {
+ final DockState dockState = (DockState) event.dropTarget;
// Hide the dock region
updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1,
@@ -731,18 +704,10 @@ public class RecentsView extends FrameLayout {
}
public final void onBusEvent(ShowStackActionButtonEvent event) {
- if (!RecentsDebugFlags.Static.EnableStackActionButton) {
- return;
- }
-
showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
}
public final void onBusEvent(HideStackActionButtonEvent event) {
- if (!RecentsDebugFlags.Static.EnableStackActionButton) {
- return;
- }
-
hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
}
@@ -758,10 +723,6 @@ public class RecentsView extends FrameLayout {
* Shows the stack action button.
*/
private void showStackActionButton(final int duration, final boolean translate) {
- if (!RecentsDebugFlags.Static.EnableStackActionButton) {
- return;
- }
-
final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
if (mStackActionButton.getVisibility() == View.INVISIBLE) {
mStackActionButton.setVisibility(View.VISIBLE);
@@ -794,10 +755,6 @@ public class RecentsView extends FrameLayout {
* Hides the stack action button.
*/
private void hideStackActionButton(int duration, boolean translate) {
- if (!RecentsDebugFlags.Static.EnableStackActionButton) {
- return;
- }
-
final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
hideStackActionButton(duration, translate, postAnimationTrigger);
postAnimationTrigger.flushLastDecrementRunnables();
@@ -808,10 +765,6 @@ public class RecentsView extends FrameLayout {
*/
private void hideStackActionButton(int duration, boolean translate,
final ReferenceCountedTrigger postAnimationTrigger) {
- if (!RecentsDebugFlags.Static.EnableStackActionButton) {
- return;
- }
-
if (mStackActionButton.getVisibility() == View.VISIBLE) {
if (translate) {
mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight()
@@ -858,15 +811,15 @@ public class RecentsView extends FrameLayout {
/**
* Updates the dock region to match the specified dock state.
*/
- private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates,
+ private void updateVisibleDockRegions(DockState[] newDockStates,
boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha,
boolean animateAlpha, boolean animateBounds) {
- ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
- new ArraySet<TaskStack.DockState>());
- ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+ ArraySet<DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
+ new ArraySet<DockState>());
+ ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
for (int i = visDockStates.size() - 1; i >= 0; i--) {
- TaskStack.DockState dockState = visDockStates.get(i);
- TaskStack.DockState.ViewState viewState = dockState.viewState;
+ DockState dockState = visDockStates.get(i);
+ DockState.ViewState viewState = dockState.viewState;
if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
// This is no longer visible, so hide it
viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION,
diff --git a/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index b6b24bcd..0cfdbdec 100644
--- a/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -17,7 +17,6 @@
package com.android.systemui.recents.views;
import android.app.ActivityManager;
-import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.InputDevice;
@@ -32,7 +31,6 @@ 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;
@@ -41,8 +39,8 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import java.util.ArrayList;
@@ -72,7 +70,7 @@ public class RecentsViewTouchHandler {
private DropTarget mLastDropTarget;
private DividerSnapAlgorithm mDividerSnapAlgorithm;
private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
- private ArrayList<TaskStack.DockState> mVisibleDockStates = new ArrayList<>();
+ private ArrayList<DockState> mVisibleDockStates = new ArrayList<>();
public RecentsViewTouchHandler(RecentsView rv) {
mRv = rv;
@@ -96,7 +94,7 @@ public class RecentsViewTouchHandler {
/**
* Returns the set of visible dock states for this current drag.
*/
- public ArrayList<TaskStack.DockState> getVisibleDockStates() {
+ public ArrayList<DockState> getVisibleDockStates() {
return mVisibleDockStates;
}
@@ -148,9 +146,9 @@ public class RecentsViewTouchHandler {
EventBus.getDefault().send(new ShowIncompatibleAppOverlayEvent());
} else {
// Add the dock state drop targets (these take priority)
- TaskStack.DockState[] dockStates = Recents.getConfiguration()
+ DockState[] dockStates = Recents.getConfiguration()
.getDockStatesForCurrentOrientation();
- for (TaskStack.DockState dockState : dockStates) {
+ for (DockState dockState : dockStates) {
registerDropTargetForCurrentDrag(dockState);
dockState.update(mRv.getContext());
mVisibleDockStates.add(dockState);
diff --git a/com/android/systemui/recents/views/SystemBarScrimViews.java b/com/android/systemui/recents/views/SystemBarScrimViews.java
index 8f784b83..7827c590 100644
--- a/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -30,7 +30,7 @@ import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
/** Manages the scrims for the various system bars. */
public class SystemBarScrimViews {
@@ -159,7 +159,7 @@ public class SystemBarScrimViews {
public final void onBusEvent(final DragEndEvent event) {
// Hide the nav bar scrims once we drop to a dock region
- if (event.dropTarget instanceof TaskStack.DockState) {
+ if (event.dropTarget instanceof DockState) {
animateScrimToCurrentNavBarState(false /* hasStackTasks */);
}
}
diff --git a/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 81bf6aff..26db26fa 100644
--- a/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -18,13 +18,11 @@ package com.android.systemui.recents.views;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.Log;
-import android.view.View;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -37,9 +35,10 @@ import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
import java.util.ArrayList;
import java.util.List;
@@ -161,20 +160,12 @@ public class TaskStackAnimationHelper {
for (int i = taskViews.size() - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- boolean currentTaskOccludesLaunchTarget = launchTargetTask != null &&
- launchTargetTask.group != null &&
- launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
- boolean hideTask = launchTargetTask != null &&
- launchTargetTask.isFreeformTask() &&
- task.isFreeformTask();
// Get the current transform for the task, which will be used to position it offscreen
stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
null);
- if (hideTask) {
- tv.setVisibility(View.INVISIBLE);
- } else if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
+ if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
if (task.isLaunchTarget) {
tv.onPrepareLaunchTargetForEnterAnimation();
} else if (isLowRamDevice && i >= taskViews.size() -
@@ -195,13 +186,6 @@ public class TaskStackAnimationHelper {
// com.android.server.wm.AppTransition#DEFAULT_APP_TRANSITION_DURATION}
mStackView.updateTaskViewToTransform(tv, mTmpTransform,
new AnimationProps(336, Interpolators.FAST_OUT_SLOW_IN));
- } else if (currentTaskOccludesLaunchTarget) {
- // Move the task view slightly lower so we can animate it in
- mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
- mTmpTransform.alpha = 0f;
- mStackView.updateTaskViewToTransform(tv, mTmpTransform,
- AnimationProps.IMMEDIATE);
- tv.setClipViewInStack(false);
}
} else if (launchState.launchedFromHome) {
if (isLowRamDevice) {
@@ -266,9 +250,6 @@ public class TaskStackAnimationHelper {
int taskIndexFromBack = i;
final TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- boolean currentTaskOccludesLaunchTarget = launchTargetTask != null &&
- launchTargetTask.group != null &&
- launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
// Get the current transform for the task, which will be updated to the final transform
// to animate to depending on how recents was invoked
@@ -280,21 +261,6 @@ public class TaskStackAnimationHelper {
tv.onStartLaunchTargetEnterAnimation(mTmpTransform,
taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled,
postAnimationTrigger);
- } else {
- // Animate the task up if it was occluding the launch target
- if (currentTaskOccludesLaunchTarget) {
- AnimationProps taskAnimation = new AnimationProps(
- taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN,
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- postAnimationTrigger.decrement();
- tv.setClipViewInStack(true);
- }
- });
- postAnimationTrigger.increment();
- mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
- }
}
} else if (launchState.launchedFromHome) {
@@ -423,9 +389,6 @@ public class TaskStackAnimationHelper {
for (int i = 0; i < taskViewCount; i++) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- boolean currentTaskOccludesLaunchTarget = launchingTask != null &&
- launchingTask.group != null &&
- launchingTask.group.isTaskAboveTask(task, launchingTask);
if (tv == launchingTaskView) {
tv.setClipViewInStack(false);
@@ -437,17 +400,6 @@ public class TaskStackAnimationHelper {
});
tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration,
screenPinningRequested, postAnimationTrigger);
- } else if (currentTaskOccludesLaunchTarget) {
- // Animate this task out of view
- AnimationProps taskAnimation = new AnimationProps(
- taskViewExitToAppDuration, Interpolators.ALPHA_OUT,
- postAnimationTrigger.decrementOnAnimationEnd());
- postAnimationTrigger.increment();
-
- mTmpTransform.fillIn(tv);
- mTmpTransform.alpha = 0f;
- mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
- mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
}
}
}
@@ -611,7 +563,7 @@ public class TaskStackAnimationHelper {
false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
// Hide the front most task view until the scroll is complete
- Task frontMostTask = newStack.getStackFrontMostTask(false /* includeFreeform */);
+ Task frontMostTask = newStack.getStackFrontMostTask();
final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask);
final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get(
stackTasks.indexOf(frontMostTask));
diff --git a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index eaa32eef..acb058ce 100644
--- a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -24,7 +24,6 @@ import android.graphics.Path;
import android.graphics.Rect;
import android.util.ArraySet;
import android.util.Log;
-import android.util.MutableFloat;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.ViewDebug;
@@ -36,9 +35,9 @@ import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.misc.FreePathInterpolator;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
@@ -147,75 +146,6 @@ public class TaskStackLayoutAlgorithm {
}
/**
- * The various stack/freeform states.
- */
- public static class StackState {
-
- public static final StackState FREEFORM_ONLY = new StackState(1f, 255);
- public static final StackState STACK_ONLY = new StackState(0f, 0);
- public static final StackState SPLIT = new StackState(0.5f, 255);
-
- public final float freeformHeightPct;
- public final int freeformBackgroundAlpha;
-
- /**
- * @param freeformHeightPct the percentage of the stack height (not including paddings) to
- * allocate to the freeform workspace
- * @param freeformBackgroundAlpha the background alpha for the freeform workspace
- */
- private StackState(float freeformHeightPct, int freeformBackgroundAlpha) {
- this.freeformHeightPct = freeformHeightPct;
- this.freeformBackgroundAlpha = freeformBackgroundAlpha;
- }
-
- /**
- * Resolves the stack state for the layout given a task stack.
- */
- public static StackState getStackStateForStack(TaskStack stack) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- boolean hasFreeformWorkspaces = ssp.hasFreeformWorkspaceSupport();
- int freeformCount = stack.getFreeformTaskCount();
- int stackCount = stack.getStackTaskCount();
- if (hasFreeformWorkspaces && stackCount > 0 && freeformCount > 0) {
- return SPLIT;
- } else if (hasFreeformWorkspaces && freeformCount > 0) {
- return FREEFORM_ONLY;
- } else {
- return STACK_ONLY;
- }
- }
-
- /**
- * Computes the freeform and stack rect for this state.
- *
- * @param freeformRectOut the freeform rect to be written out
- * @param stackRectOut the stack rect, we only write out the top of the stack
- * @param taskStackBounds the full rect that the freeform rect can take up
- */
- public void computeRects(Rect freeformRectOut, Rect stackRectOut,
- Rect taskStackBounds, int topMargin, int freeformGap, int stackBottomOffset) {
- // The freeform height is the visible height (not including system insets) - padding
- // above freeform and below stack - gap between the freeform and stack
- int availableHeight = taskStackBounds.height() - topMargin - stackBottomOffset;
- int ffPaddedHeight = (int) (availableHeight * freeformHeightPct);
- int ffHeight = Math.max(0, ffPaddedHeight - freeformGap);
- freeformRectOut.set(taskStackBounds.left,
- taskStackBounds.top + topMargin,
- taskStackBounds.right,
- taskStackBounds.top + topMargin + ffHeight);
- stackRectOut.set(taskStackBounds.left,
- taskStackBounds.top,
- taskStackBounds.right,
- taskStackBounds.bottom);
- if (ffPaddedHeight > 0) {
- stackRectOut.top += ffPaddedHeight;
- } else {
- stackRectOut.top += topMargin;
- }
- }
- }
-
- /**
* @return True if we should use the grid layout.
*/
boolean useGridLayout() {
@@ -234,15 +164,11 @@ public class TaskStackLayoutAlgorithm {
}
Context mContext;
- private StackState mState = StackState.SPLIT;
private TaskStackLayoutAlgorithmCallbacks mCb;
// The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot.
@ViewDebug.ExportedProperty(category="recents")
public Rect mTaskRect = new Rect();
- // The freeform workspace bounds, inset by the top system insets and is a fixed height
- @ViewDebug.ExportedProperty(category="recents")
- public Rect mFreeformRect = new Rect();
// The stack bounds, inset from the top system insets, and runs to the bottom of the screen
@ViewDebug.ExportedProperty(category="recents")
public Rect mStackRect = new Rect();
@@ -268,10 +194,6 @@ public class TaskStackLayoutAlgorithm {
private int mBaseBottomMargin;
private int mMinMargin;
- // The gap between the freeform and stack layouts
- @ViewDebug.ExportedProperty(category="recents")
- private int mFreeformStackGap;
-
// The initial offset that the focused task is from the top
@ViewDebug.ExportedProperty(category="recents")
private int mInitialTopOffset;
@@ -331,8 +253,6 @@ public class TaskStackLayoutAlgorithm {
// The last computed task counts
@ViewDebug.ExportedProperty(category="recents")
int mNumStackTasks;
- @ViewDebug.ExportedProperty(category="recents")
- int mNumFreeformTasks;
// The min/max z translations
@ViewDebug.ExportedProperty(category="recents")
@@ -344,8 +264,6 @@ public class TaskStackLayoutAlgorithm {
private SparseIntArray mTaskIndexMap = new SparseIntArray();
private SparseArray<Float> mTaskIndexOverrideMap = new SparseArray<>();
- // The freeform workspace layout
- FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
TaskStackLowRamLayoutAlgorithm mTaskStackLowRamLayoutAlgorithm;
@@ -356,7 +274,6 @@ public class TaskStackLayoutAlgorithm {
public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
mContext = context;
mCb = cb;
- mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context);
mTaskStackLowRamLayoutAlgorithm = new TaskStackLowRamLayoutAlgorithm(context);
reloadOnConfigurationChange(context);
@@ -393,7 +310,6 @@ public class TaskStackLayoutAlgorithm {
R.dimen.recents_layout_initial_bottom_offset_tablet,
R.dimen.recents_layout_initial_bottom_offset_tablet,
R.dimen.recents_layout_initial_bottom_offset_tablet);
- mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context);
mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context);
mTaskStackLowRamLayoutAlgorithm.reloadOnConfigurationChange(context);
mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
@@ -408,8 +324,6 @@ public class TaskStackLayoutAlgorithm {
R.dimen.recents_layout_side_margin_tablet_xlarge,
R.dimen.recents_layout_side_margin_tablet);
mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
- mFreeformStackGap =
- res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin);
mTitleBarHeight = getDimensionForDevice(mContext,
R.dimen.recents_task_view_header_height,
R.dimen.recents_task_view_header_height,
@@ -462,8 +376,7 @@ public class TaskStackLayoutAlgorithm {
* Computes the stack and task rects. The given task stack bounds already has the top/right
* insets and left/right padding already applied.
*/
- public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds,
- StackState state) {
+ public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds) {
Rect lastStackRect = new Rect(mStackRect);
int topMargin = getScaleForExtent(windowRect, displayRect, mBaseTopMargin, mMinMargin, HEIGHT);
@@ -474,10 +387,9 @@ public class TaskStackLayoutAlgorithm {
mInitialBottomOffset = mBaseInitialBottomOffset;
// Compute the stack bounds
- mState = state;
mStackBottomOffset = mSystemInsets.bottom + bottomMargin;
- state.computeRects(mFreeformRect, mStackRect, taskStackBounds, topMargin,
- mFreeformStackGap, mStackBottomOffset);
+ mStackRect.set(taskStackBounds);
+ mStackRect.top += topMargin;
// The stack action button will take the full un-padded header space above the stack
mStackActionButtonRect.set(mStackRect.left, mStackRect.top - topMargin,
@@ -530,26 +442,20 @@ public class TaskStackLayoutAlgorithm {
if (tasks.isEmpty()) {
mFrontMostTaskP = 0;
mMinScrollP = mMaxScrollP = mInitialScrollP = 0;
- mNumStackTasks = mNumFreeformTasks = 0;
+ mNumStackTasks = 0;
return;
}
- // Filter the set of freeform and stack tasks
- ArrayList<Task> freeformTasks = new ArrayList<>();
+ // Filter the set of stack tasks
ArrayList<Task> stackTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if (ignoreTasksSet.contains(task.key)) {
continue;
}
- if (task.isFreeformTask()) {
- freeformTasks.add(task);
- } else {
- stackTasks.add(task);
- }
+ stackTasks.add(task);
}
mNumStackTasks = stackTasks.size();
- mNumFreeformTasks = freeformTasks.size();
// Put each of the tasks in the progress map at a fixed index (does not need to actually
// map to a scroll position, just by index)
@@ -559,11 +465,6 @@ public class TaskStackLayoutAlgorithm {
mTaskIndexMap.put(task.key.id, i);
}
- // Update the freeform tasks
- if (!freeformTasks.isEmpty()) {
- mFreeformLayoutAlgorithm.update(freeformTasks, this);
- }
-
// Calculate the min/max/initial scroll
Task launchTask = stack.getLaunchTarget();
int launchTaskIndex = launchTask != null
@@ -582,7 +483,7 @@ public class TaskStackLayoutAlgorithm {
} else {
mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
}
- } else if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+ } else if (mNumStackTasks == 1) {
// If there is one stack task, ignore the min/max/initial scroll positions
mMinScrollP = 0;
mMaxScrollP = 0;
@@ -603,9 +504,7 @@ public class TaskStackLayoutAlgorithm {
boolean scrollToFront = launchState.launchedFromHome || launchState.launchedFromPipApp
|| launchState.launchedWithNextPipApp || launchState.launchedViaDockGesture;
- if (launchState.launchedFromBlacklistedApp) {
- mInitialScrollP = mMaxScrollP;
- } else if (launchState.launchedWithAltTab) {
+ if (launchState.launchedWithAltTab) {
mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
} else if (Recents.getConfiguration().isLowRamDevice) {
mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
@@ -633,7 +532,6 @@ public class TaskStackLayoutAlgorithm {
boolean scrollToFront = launchState.launchedFromHome ||
launchState.launchedFromPipApp ||
launchState.launchedWithNextPipApp ||
- launchState.launchedFromBlacklistedApp ||
launchState.launchedViaDockGesture;
if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) {
if (ignoreScrollToFront || (!launchState.launchedWithAltTab && !scrollToFront)) {
@@ -767,7 +665,7 @@ public class TaskStackLayoutAlgorithm {
public int getInitialFocusState() {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
RecentsDebugFlags debugFlags = Recents.getDebugFlags();
- if (debugFlags.isPagingEnabled() || launchState.launchedWithAltTab) {
+ if (launchState.launchedWithAltTab) {
return STATE_FOCUSED;
} else {
return STATE_UNFOCUSED;
@@ -794,13 +692,6 @@ public class TaskStackLayoutAlgorithm {
}
/**
- * Returns the current stack state.
- */
- public StackState getStackState() {
- return mState;
- }
-
- /**
* Returns whether this stack layout has been initialized.
*/
public boolean isInitialized() {
@@ -825,62 +716,44 @@ public class TaskStackLayoutAlgorithm {
return new VisibilityReport(1, 1);
}
- // Quick return when there are no stack tasks
- if (mNumStackTasks == 0) {
- return new VisibilityReport(mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0,
- mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0);
- }
-
// Otherwise, walk backwards in the stack and count the number of tasks and visible
- // thumbnails and add that to the total freeform task count
+ // thumbnails and add that to the total task count
TaskViewTransform tmpTransform = new TaskViewTransform();
Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
currentRange.offset(mInitialScrollP);
int taskBarHeight = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_task_view_header_height);
- int numVisibleTasks = mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0;
- int numVisibleThumbnails = mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 0) : 0;
+ int numVisibleTasks = 0;
+ int numVisibleThumbnails = 0;
float prevScreenY = Integer.MAX_VALUE;
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
- // Skip freeform
- if (task.isFreeformTask()) {
- continue;
- }
-
// Skip invisible
float taskProgress = getStackScrollForTask(task);
if (!currentRange.isInRange(taskProgress)) {
continue;
}
- boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
- if (isFrontMostTaskInGroup) {
- getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
- tmpTransform, null, false /* ignoreSingleTaskCase */,
- false /* forceUpdate */);
- float screenY = tmpTransform.rect.top;
- boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
- if (hasVisibleThumbnail) {
- numVisibleThumbnails++;
- numVisibleTasks++;
- prevScreenY = screenY;
- } else {
- // Once we hit the next front most task that does not have a visible thumbnail,
- // walk through remaining visible set
- for (int j = i; j >= 0; j--) {
- taskProgress = getStackScrollForTask(tasks.get(j));
- if (!currentRange.isInRange(taskProgress)) {
- break;
- }
- numVisibleTasks++;
+ getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
+ tmpTransform, null, false /* ignoreSingleTaskCase */, false /* forceUpdate */);
+ float screenY = tmpTransform.rect.top;
+ boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
+ if (hasVisibleThumbnail) {
+ numVisibleThumbnails++;
+ numVisibleTasks++;
+ prevScreenY = screenY;
+ } else {
+ // Once we hit the next front most task that does not have a visible thumbnail,
+ // walk through remaining visible set
+ for (int j = i; j >= 0; j--) {
+ taskProgress = getStackScrollForTask(tasks.get(j));
+ if (!currentRange.isInRange(taskProgress)) {
+ break;
}
- break;
+ numVisibleTasks++;
}
- } else {
- // Affiliated task, no thumbnail
- numVisibleTasks++;
+ break;
}
}
return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
@@ -906,10 +779,7 @@ public class TaskStackLayoutAlgorithm {
public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
boolean ignoreTaskOverrides) {
- if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
- mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
- return transformOut;
- } else if (useGridLayout()) {
+ if (useGridLayout()) {
int taskIndex = mTaskIndexMap.get(task.key.id);
int taskCount = mTaskIndexMap.size();
mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this);
@@ -1024,7 +894,7 @@ public class TaskStackLayoutAlgorithm {
float z;
float dimAlpha;
float viewOutlineAlpha;
- if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
+ if (mNumStackTasks == 1 && !ignoreSingleTaskCase) {
// When there is exactly one task, then decouple the task from the stack and just move
// in screen space
float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks;
@@ -1378,7 +1248,6 @@ public class TaskStackLayoutAlgorithm {
writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets));
writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect));
writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect));
- writer.print(" freeform="); writer.print(Utilities.dumpRect(mFreeformRect));
writer.print(" actionButton="); writer.print(Utilities.dumpRect(mStackActionButtonRect));
writer.println();
diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java
index 3160ee0e..428113a2 100644
--- a/com/android/systemui/recents/views/TaskStackView.java
+++ b/com/android/systemui/recents/views/TaskStackView.java
@@ -16,22 +16,15 @@
package com.android.systemui.recents.views;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Canvas;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.provider.Settings;
import android.util.ArrayMap;
@@ -64,7 +57,6 @@ import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimatio
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
-import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
@@ -83,13 +75,11 @@ import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
@@ -97,9 +87,10 @@ import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.grid.GridTaskView;
import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
@@ -153,8 +144,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
private TaskStackViewTouchHandler mTouchHandler;
private TaskStackAnimationHelper mAnimationHelper;
- private GradientDrawable mFreeformWorkspaceBackground;
- private ObjectAnimator mFreeformWorkspaceBackgroundAnimator;
private ViewPool<TaskView, Task> mViewPool;
private ArrayList<TaskView> mTaskViews = new ArrayList<>();
@@ -239,20 +228,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
};
- // The drop targets for a task drag
- private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() {
- @Override
- public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
- boolean isCurrentTarget) {
- // This drop target has a fixed bounds and should be checked last, so just fall through
- // if it is the current target
- if (!isCurrentTarget) {
- return mLayoutAlgorithm.mFreeformRect.contains(x, y);
- }
- return false;
- }
- };
-
private DropTarget mStackDropTarget = new DropTarget() {
@Override
public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
@@ -312,17 +287,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
});
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- if (ssp.hasFreeformWorkspaceSupport()) {
- setWillNotDraw(false);
- }
-
- mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable(
- R.drawable.recents_freeform_workspace_bg);
- mFreeformWorkspaceBackground.setCallback(this);
- if (ssp.hasFreeformWorkspaceSupport()) {
- mFreeformWorkspaceBackground.setColor(
- getContext().getColor(R.color.recents_freeform_workspace_bg_color));
- }
}
@Override
@@ -359,12 +323,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
readSystemFlags();
mTaskViewsClipDirty = true;
mUIDozeTrigger.stopDozing();
- if (isResumingFromVisible) {
- // Animate in the freeform workspace
- int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
- animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
- Interpolators.FAST_OUT_SLOW_IN));
- } else {
+ if (!isResumingFromVisible) {
mStackScroller.reset();
mStableLayoutAlgorithm.reset();
mLayoutAlgorithm.reset();
@@ -387,7 +346,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Only notify if we are already initialized, otherwise, everything will pick up all the
// new and old tasks when we next layout
- mStack.setTasks(getContext(), stack, allowNotifyStackChanges && isInitialized);
+ mStack.setTasks(stack, allowNotifyStackChanges && isInitialized);
}
/** Returns the task stack. */
@@ -422,23 +381,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/**
* Returns the front most task view.
- *
- * @param stackTasksOnly if set, will return the front most task view in the stack (by default
- * the front most task view will be freeform since they are placed above
- * stack tasks)
*/
- private TaskView getFrontMostTaskView(boolean stackTasksOnly) {
+ private TaskView getFrontMostTaskView() {
List<TaskView> taskViews = getTaskViews();
- int taskViewCount = taskViews.size();
- for (int i = taskViewCount - 1; i >= 0; i--) {
- TaskView tv = taskViews.get(i);
- Task task = tv.getTask();
- if (stackTasksOnly && task.isFreeformTask()) {
- continue;
- }
- return tv;
+ if (taskViews.isEmpty()) {
+ return null;
}
- return null;
+ return taskViews.get(taskViews.size() - 1);
}
/**
@@ -500,8 +449,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
* visible range includes all tasks at the target stack scroll. This is useful for ensure that
* all views necessary for a transition or animation will be visible at the start.
*
- * This call ignores freeform tasks.
- *
* @param taskTransforms The set of task view transforms to reuse, this list will be sized to
* match the size of {@param tasks}
* @param tasks The set of tasks for which to generate transforms
@@ -524,7 +471,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
// We can reuse the task transforms where possible to reduce object allocation
- Utilities.matchTaskListSize(tasks, taskTransforms);
+ matchTaskListSize(tasks, taskTransforms);
// Update the stack transforms
TaskViewTransform frontTransform = null;
@@ -554,12 +501,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
continue;
}
- // For freeform tasks, only calculate the stack transform and skip the calculation of
- // the visible stack indices
- if (task.isFreeformTask()) {
- continue;
- }
-
frontTransform = transform;
frontTransformAtTarget = transformAtTarget;
if (transform.visible) {
@@ -622,7 +563,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
transform = mCurrentTaskTransforms.get(taskIndex);
}
- if (task.isFreeformTask() || (transform != null && transform.visible)) {
+ if (transform != null && transform.visible) {
mTmpTaskViewMap.put(task.key, tv);
} else {
if (mTouchExplorationEnabled && Utilities.isDescendentAccessibilityFocused(tv)) {
@@ -643,24 +584,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
continue;
}
- // Skip the invisible non-freeform stack tasks
- if (!task.isFreeformTask() && !transform.visible) {
+ // Skip the invisible stack tasks
+ if (!transform.visible) {
continue;
}
TaskView tv = mTmpTaskViewMap.get(task.key);
if (tv == null) {
tv = mViewPool.pickUpViewFromPool(task, task);
- if (task.isFreeformTask()) {
- updateTaskViewToTransform(tv, transform, AnimationProps.IMMEDIATE);
+ if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) {
+ updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(),
+ AnimationProps.IMMEDIATE);
} else {
- if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) {
- updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(),
- AnimationProps.IMMEDIATE);
- } else {
- updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(),
- AnimationProps.IMMEDIATE);
- }
+ updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(),
+ AnimationProps.IMMEDIATE);
}
} else {
// Reattach it in the right z order
@@ -764,7 +701,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
*/
public void getCurrentTaskTransforms(ArrayList<Task> tasks,
ArrayList<TaskViewTransform> transformsOut) {
- Utilities.matchTaskListSize(tasks, transformsOut);
+ matchTaskListSize(tasks, transformsOut);
int focusState = mLayoutAlgorithm.getFocusState();
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
@@ -787,7 +724,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
*/
public void getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks,
boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut) {
- Utilities.matchTaskListSize(tasks, transformsOut);
+ matchTaskListSize(tasks, transformsOut);
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
TaskViewTransform transform = transformsOut.get(i);
@@ -887,13 +824,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Compute the min and max scroll values
mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState);
- // Update the freeform workspace background
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.hasFreeformWorkspaceSupport()) {
- mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
- mFreeformWorkspaceBackground.setBounds(mTmpRect);
- }
-
if (boundScrollToNewMinMax) {
mStackScroller.boundScroll();
}
@@ -906,8 +836,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mWindowRect.set(mStableWindowRect);
mStackBounds.set(mStableStackBounds);
mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets);
- mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+ mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
updateLayoutAlgorithm(true /* boundScroll */);
}
@@ -1028,21 +957,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (focusedTask != null) {
if (stackTasksOnly) {
List<Task> tasks = mStack.getStackTasks();
- if (focusedTask.isFreeformTask()) {
- // Try and focus the front most stack task
- TaskView tv = getFrontMostTaskView(stackTasksOnly);
- if (tv != null) {
- newIndex = mStack.indexOfStackTask(tv.getTask());
- }
- } else {
- // Try the next task if it is a stack task
- int tmpNewIndex = newIndex + (forward ? -1 : 1);
- if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
- Task t = tasks.get(tmpNewIndex);
- if (!t.isFreeformTask()) {
- newIndex = tmpNewIndex;
- }
- }
+ // Try the next task if it is a stack task
+ int tmpNewIndex = newIndex + (forward ? -1 : 1);
+ if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
+ newIndex = tmpNewIndex;
}
} else {
// No restrictions, lets just move to the new task (looping forward/backwards if
@@ -1127,7 +1045,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
return tv.getTask();
}
}
- TaskView frontTv = getFrontMostTaskView(true /* stackTasksOnly */);
+ TaskView frontTv = getFrontMostTaskView();
if (frontTv != null) {
return frontTv.getTask();
}
@@ -1278,10 +1196,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Compute the rects in the stack algorithm
- mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
- mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+ mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds);
+ mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
updateLayoutAlgorithm(false /* boundScroll */);
// If this is the first layout, then scroll to the front of the stack, then update the
@@ -1404,11 +1320,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Setup the view for the enter animation
mAnimationHelper.prepareForEnterAnimation();
- // Animate in the freeform workspace
- int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
- animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
- Interpolators.FAST_OUT_SLOW_IN));
-
// Set the task focused state without requesting view focus, and leave the focus animations
// until after the enter-animation
RecentsConfiguration config = Recents.getConfiguration();
@@ -1456,43 +1367,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
return null;
}
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- // Draw the freeform workspace background
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.hasFreeformWorkspaceSupport()) {
- if (mFreeformWorkspaceBackground.getAlpha() > 0) {
- mFreeformWorkspaceBackground.draw(canvas);
- }
- }
- }
-
- @Override
- protected boolean verifyDrawable(Drawable who) {
- if (who == mFreeformWorkspaceBackground) {
- return true;
- }
- return super.verifyDrawable(who);
- }
-
- /**
- * Launches the freeform tasks.
- */
- public boolean launchFreeformTasks() {
- ArrayList<Task> tasks = mStack.getFreeformTasks();
- if (!tasks.isEmpty()) {
- Task frontTask = tasks.get(tasks.size() - 1);
- if (frontTask != null && frontTask.isFreeformTask()) {
- EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask),
- frontTask, null, false));
- return true;
- }
- }
- return false;
- }
-
/**** TaskStackCallbacks Implementation ****/
@Override
@@ -1671,8 +1545,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Restore the action button visibility if it is the front most task view
- if (mScreenPinningEnabled && tv.getTask() ==
- mStack.getStackFrontMostTask(false /* includeFreeform */)) {
+ if (mScreenPinningEnabled && tv.getTask() == mStack.getStackFrontMostTask()) {
tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
}
}
@@ -1688,7 +1561,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// If the doze trigger has already fired, then update the state for this task view
if (mUIDozeTrigger.isAsleep() ||
- Recents.getSystemServices().hasFreeformWorkspaceSupport() ||
useGridLayout() || Recents.getConfiguration().isLowRamDevice) {
tv.setNoUserInteractionState();
}
@@ -1820,21 +1692,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public final void onBusEvent(LaunchMostRecentTaskRequestEvent event) {
if (mStack.getTaskCount() > 0) {
- Task mostRecentTask = mStack.getStackFrontMostTask(true /* includeFreefromTasks */);
+ Task mostRecentTask = mStack.getStackFrontMostTask();
launchTask(mostRecentTask);
}
}
public final void onBusEvent(ShowStackActionButtonEvent event) {
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- mStackActionButtonVisible = true;
- }
+ mStackActionButtonVisible = true;
}
public final void onBusEvent(HideStackActionButtonEvent event) {
- if (RecentsDebugFlags.Static.EnableStackActionButton) {
- mStackActionButtonVisible = false;
- }
+ mStackActionButtonVisible = false;
}
public final void onBusEvent(LaunchNextTaskRequestEvent event) {
@@ -1891,11 +1759,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Start the task animations
mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger());
- // Dismiss the freeform workspace background
- int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
- animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration,
- Interpolators.FAST_OUT_SLOW_IN));
-
// Dismiss the grid task view focus frame
if (mTaskViewFocusFrame != null) {
mTaskViewFocusFrame.moveGridTaskViewFocus(null);
@@ -1977,8 +1840,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mStackScroller.stopScroller();
mStackScroller.stopBoundScrollAnimation();
- setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false,
- event.timerIndicatorDuration);
+ setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, 0);
}
public final void onBusEvent(FocusPreviousTaskViewEvent event) {
@@ -2002,8 +1864,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
break;
case DOWN:
- EventBus.getDefault().send(
- new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */));
+ EventBus.getDefault().send(new FocusNextTaskViewEvent());
break;
}
}
@@ -2014,7 +1875,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mUIDozeTrigger.poke();
RecentsDebugFlags debugFlags = Recents.getDebugFlags();
- if (debugFlags.isFastToggleRecentsEnabled() && mFocusedTask != null) {
+ if (mFocusedTask != null) {
TaskView tv = getChildViewForTask(mFocusedTask);
if (tv != null) {
tv.getHeaderView().cancelFocusTimerIndicator();
@@ -2026,11 +1887,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Ensure that the drag task is not animated
addIgnoreTask(event.task);
- if (event.task.isFreeformTask()) {
- // Animate to the front of the stack
- mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null);
- }
-
// Enlarge the dragged view slightly
float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR;
mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
@@ -2042,22 +1898,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
}
- public final void onBusEvent(DragStartInitializeDropTargetsEvent event) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.hasFreeformWorkspaceSupport()) {
- event.handler.registerDropTargetForCurrentDrag(mStackDropTarget);
- event.handler.registerDropTargetForCurrentDrag(mFreeformWorkspaceDropTarget);
- }
- }
-
public final void onBusEvent(DragDropTargetChangedEvent event) {
AnimationProps animation = new AnimationProps(SLOW_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN);
boolean ignoreTaskOverrides = false;
- if (event.dropTarget instanceof TaskStack.DockState) {
+ if (event.dropTarget instanceof DockState) {
// Calculate the new task stack bounds that matches the window size that Recents will
// have after the drop
- final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
+ final DockState dockState = (DockState) event.dropTarget;
Rect systemInsets = new Rect(mStableLayoutAlgorithm.mSystemInsets);
// When docked, the nav bar insets are consumed and the activity is measured without
// insets. However, the window bounds include the insets, so we need to subtract them
@@ -2069,8 +1917,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
height, mDividerSize, systemInsets,
mLayoutAlgorithm, getResources(), mWindowRect));
mLayoutAlgorithm.setSystemInsets(systemInsets);
- mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+ mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
updateLayoutAlgorithm(true /* boundScroll */);
ignoreTaskOverrides = true;
} else {
@@ -2085,39 +1932,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public final void onBusEvent(final DragEndEvent event) {
// We don't handle drops on the dock regions
- if (event.dropTarget instanceof TaskStack.DockState) {
+ if (event.dropTarget instanceof DockState) {
// However, we do need to reset the overrides, since the last state of this task stack
// view layout was ignoring task overrides (see DragDropTargetChangedEvent handler)
mLayoutAlgorithm.clearUnfocusedTaskOverrides();
return;
}
- boolean isFreeformTask = event.task.isFreeformTask();
- boolean hasChangedWindowingMode =
- (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
- (isFreeformTask && event.dropTarget == mStackDropTarget);
-
- 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.setTaskWindowingMode(event.task, WINDOWING_MODE_FREEFORM);
- } else if (event.dropTarget == mStackDropTarget) {
- mStack.setTaskWindowingMode(event.task, WINDOWING_MODE_FULLSCREEN);
- }
- updateLayoutAlgorithm(true /* boundScroll */);
-
- // Move the task to the new stack in the system after the animation completes
- event.addPostAnimationCallback(new Runnable() {
- @Override
- public void run() {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.setTaskWindowingMode(event.task.key.id, event.task.key.windowingMode);
- }
- });
- }
-
// Restore the task, so that relayout will apply to it below
removeIgnoreTask(event.task);
@@ -2152,13 +1973,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
event.getAnimationTrigger().increment();
}
- public final void onBusEvent(IterateRecentsEvent event) {
- if (!mEnterAnimationComplete) {
- // Cancel the previous task's window transition before animating the focused state
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
- }
- }
-
public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
mEnterAnimationComplete = true;
tryStartEnterAnimation();
@@ -2177,9 +1991,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Add a runnable to the post animation ref counter to clear all the views
trigger.addLastDecrementRunnable(() -> {
// Start the dozer to trigger to trigger any UI that shows after a timeout
- if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
- mUIDozeTrigger.startDozing();
- }
+ mUIDozeTrigger.startDozing();
// Update the focused state here -- since we only set the focused task without
// requesting view focus in onFirstLayout(), actually request view focus and
@@ -2202,18 +2014,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mStackReloaded = false;
}
- public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) {
- List<TaskView> taskViews = getTaskViews();
- int taskViewCount = taskViews.size();
- for (int i = 0; i < taskViewCount; i++) {
- TaskView tv = taskViews.get(i);
- Task task = tv.getTask();
- if (task.isFreeformTask()) {
- tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE);
- }
- }
- }
-
public final void onBusEvent(final MultiWindowStateChangedEvent event) {
if (event.inMultiWindow || !event.showDeferredAnimation) {
setTasks(event.stack, true /* allowNotifyStackChanges */);
@@ -2315,27 +2115,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
- * Starts an alpha animation on the freeform workspace background.
- */
- private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha,
- AnimationProps animation) {
- if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) {
- return;
- }
-
- Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator);
- mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground,
- Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha);
- mFreeformWorkspaceBackgroundAnimator.setStartDelay(
- animation.getDuration(AnimationProps.ALPHA));
- mFreeformWorkspaceBackgroundAnimator.setDuration(
- animation.getDuration(AnimationProps.ALPHA));
- mFreeformWorkspaceBackgroundAnimator.setInterpolator(
- animation.getInterpolator(AnimationProps.ALPHA));
- mFreeformWorkspaceBackgroundAnimator.start();
- }
-
- /**
* Returns the insert index for the task in the current set of task views. If the given task
* is already in the task view list, then this method returns the insert index assuming it
* is first removed at the previous index.
@@ -2421,6 +2200,24 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
+ /**
+ * Updates {@param transforms} to be the same size as {@param tasks}.
+ */
+ private void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) {
+ // We can reuse the task transforms where possible to reduce object allocation
+ int taskTransformCount = transforms.size();
+ int taskCount = tasks.size();
+ if (taskTransformCount < taskCount) {
+ // If there are less transforms than tasks, then add as many transforms as necessary
+ for (int i = taskTransformCount; i < taskCount; i++) {
+ transforms.add(new TaskViewTransform());
+ }
+ } else if (taskTransformCount > taskCount) {
+ // If there are more transforms than tasks, then just subset the transform list
+ transforms.subList(taskCount, taskTransformCount).clear();
+ }
+ }
+
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/TaskStackViewScroller.java b/com/android/systemui/recents/views/TaskStackViewScroller.java
index 0b20b105..6b239774 100644
--- a/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -24,7 +24,6 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.util.FloatProperty;
import android.util.Log;
-import android.util.MutableFloat;
import android.util.Property;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
@@ -33,7 +32,8 @@ import android.widget.OverScroller;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
import com.android.systemui.statusbar.FlingAnimationUtils;
diff --git a/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 32a249c2..b9ca2483 100644
--- a/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -21,7 +21,6 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Path;
-import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.MutableBoolean;
import android.view.InputDevice;
@@ -45,9 +44,9 @@ import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
import com.android.systemui.recents.misc.FreePathInterpolator;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.statusbar.FlingAnimationUtils;
import java.util.ArrayList;
@@ -403,18 +402,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
return;
}
- // If tapping on the freeform workspace background, just launch the first freeform task
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.hasFreeformWorkspaceSupport()) {
- Rect freeformRect = mSv.mLayoutAlgorithm.mFreeformRect;
- if (freeformRect.top <= y && y <= freeformRect.bottom) {
- if (mSv.launchFreeformTasks()) {
- // TODO: Animate Recents away as we launch the freeform tasks
- return;
- }
- }
- }
-
// The user intentionally tapped on the background, which is like a tap on the "desktop".
// Hide recents and transition to the launcher.
EventBus.getDefault().send(new HideRecentsEvent(false, true));
diff --git a/com/android/systemui/recents/views/TaskView.java b/com/android/systemui/recents/views/TaskView.java
index 9d639647..b4408474 100644
--- a/com/android/systemui/recents/views/TaskView.java
+++ b/com/android/systemui/recents/views/TaskView.java
@@ -16,8 +16,6 @@
package com.android.systemui.recents.views;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
@@ -53,10 +51,10 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -196,9 +194,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
* Called from RecentsActivity when it is relaunched.
*/
void onReload(boolean isResumingFromVisible) {
- if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
- resetNoUserInteractionState();
- }
+ resetNoUserInteractionState();
if (!isResumingFromVisible) {
resetViewProperties();
}
@@ -415,9 +411,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
* view.
*/
boolean shouldClipViewInStack() {
- // Never clip for freeform tasks or if invisible
- if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE ||
- Recents.getConfiguration().isLowRamDevice) {
+ if (getVisibility() != View.VISIBLE || Recents.getConfiguration().isLowRamDevice) {
return false;
}
return mClipViewInStack;
@@ -715,7 +709,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
/**** Events ****/
public final void onBusEvent(DragEndEvent event) {
- if (!(event.dropTarget instanceof TaskStack.DockState)) {
+ if (!(event.dropTarget instanceof DockState)) {
event.addPostAnimationCallback(() -> {
// Reset the clip state for the drag view after the end animation completes
setClipViewInStack(true);
diff --git a/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java b/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
index 0c6b6b84..0fc507b9 100644
--- a/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
+++ b/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
@@ -28,11 +28,10 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.TaskStack;
public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate {
private static final String TAG = "TaskViewAccessibilityDelegate";
@@ -61,14 +60,14 @@ public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate {
super.onInitializeAccessibilityNodeInfo(host, info);
if (ActivityManager.supportsSplitScreenMultiWindow(mTaskView.getContext())
&& !Recents.getSystemServices().hasDockedTask()) {
- TaskStack.DockState[] dockStates = Recents.getConfiguration()
+ DockState[] dockStates = Recents.getConfiguration()
.getDockStatesForCurrentOrientation();
- for (TaskStack.DockState dockState: dockStates) {
- if (dockState == TaskStack.DockState.TOP) {
+ for (DockState dockState: dockStates) {
+ if (dockState == DockState.TOP) {
info.addAction(mActions.get(SPLIT_TASK_TOP));
- } else if (dockState == TaskStack.DockState.LEFT) {
+ } else if (dockState == DockState.LEFT) {
info.addAction(mActions.get(SPLIT_TASK_LEFT));
- } else if (dockState == TaskStack.DockState.RIGHT) {
+ } else if (dockState == DockState.RIGHT) {
info.addAction(mActions.get(SPLIT_TASK_RIGHT));
}
}
@@ -78,11 +77,11 @@ public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate {
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
if (action == SPLIT_TASK_TOP) {
- simulateDragIntoMultiwindow(TaskStack.DockState.TOP);
+ simulateDragIntoMultiwindow(DockState.TOP);
} else if (action == SPLIT_TASK_LEFT) {
- simulateDragIntoMultiwindow(TaskStack.DockState.LEFT);
+ simulateDragIntoMultiwindow(DockState.LEFT);
} else if (action == SPLIT_TASK_RIGHT) {
- simulateDragIntoMultiwindow(TaskStack.DockState.RIGHT);
+ simulateDragIntoMultiwindow(DockState.RIGHT);
} else {
return super.performAccessibilityAction(host, action, args);
}
@@ -90,8 +89,7 @@ public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate {
}
/** Simulate a user drag event to split the screen to the respected side */
- private void simulateDragIntoMultiwindow(TaskStack.DockState dockState) {
- int orientation = Utilities.getAppConfiguration(mTaskView.getContext()).orientation;
+ private void simulateDragIntoMultiwindow(DockState dockState) {
EventBus.getDefault().send(new DragStartEvent(mTaskView.getTask(), mTaskView,
new Point(0,0), false /* isUserTouchInitiated */));
EventBus.getDefault().send(new DragEndEvent(mTaskView.getTask(), mTaskView, dockState));
diff --git a/com/android/systemui/recents/views/TaskViewHeader.java b/com/android/systemui/recents/views/TaskViewHeader.java
index 198ecae2..0272a903 100644
--- a/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/com/android/systemui/recents/views/TaskViewHeader.java
@@ -17,8 +17,6 @@
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;
@@ -33,7 +31,6 @@ import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
@@ -59,8 +56,10 @@ import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
/* The task bar view */
public class TaskViewHeader extends FrameLayout
@@ -164,8 +163,6 @@ public class TaskViewHeader extends FrameLayout
float mDimAlpha;
Drawable mLightDismissDrawable;
Drawable mDarkDismissDrawable;
- Drawable mLightFreeformIcon;
- Drawable mDarkFreeformIcon;
Drawable mLightFullscreenIcon;
Drawable mDarkFullscreenIcon;
Drawable mLightInfoIcon;
@@ -173,6 +170,8 @@ public class TaskViewHeader extends FrameLayout
int mTaskBarViewLightTextColor;
int mTaskBarViewDarkTextColor;
int mDisabledTaskBarBackgroundColor;
+ String mDismissDescFormat;
+ String mAppInfoDescFormat;
int mTaskWindowingMode = WINDOWING_MODE_UNDEFINED;
// Header background
@@ -215,14 +214,15 @@ public class TaskViewHeader extends FrameLayout
mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
- mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light);
- mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark);
mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
mDisabledTaskBarBackgroundColor =
context.getColor(R.color.recents_task_bar_disabled_background_color);
+ mDismissDescFormat = mContext.getString(
+ R.string.accessibility_recents_item_will_be_dismissed);
+ mAppInfoDescFormat = mContext.getString(R.string.accessibility_recents_item_open_app_info);
// Configure the background and dim
mBackground = new HighlightColorDrawable();
@@ -249,9 +249,6 @@ public class TaskViewHeader extends FrameLayout
mIconView.setOnLongClickListener(this);
mTitleView = findViewById(R.id.title);
mDismissButton = findViewById(R.id.dismiss_task);
- if (ssp.hasFreeformWorkspaceSupport()) {
- mMoveTaskButton = findViewById(R.id.move_task);
- }
onConfigurationChanged();
}
@@ -341,20 +338,6 @@ public class TaskViewHeader extends FrameLayout
boolean showDismissIcon = true;
int rightInset = width - getMeasuredWidth();
- if (mTask != null && mTask.isFreeformTask()) {
- // For freeform tasks, we always show the app icon, and only show the title, move-task
- // icon, and the dismiss icon if there is room
- int appIconWidth = mIconView.getMeasuredWidth();
- int titleWidth = (int) mTitleView.getPaint().measureText(mTask.title);
- int dismissWidth = mDismissButton.getMeasuredWidth();
- int moveTaskWidth = mMoveTaskButton != null
- ? mMoveTaskButton.getMeasuredWidth()
- : 0;
- showTitle = width >= (appIconWidth + dismissWidth + moveTaskWidth + titleWidth);
- showMoveIcon = width >= (appIconWidth + dismissWidth + moveTaskWidth);
- showDismissIcon = width >= (appIconWidth + dismissWidth);
- }
-
mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE);
if (mMoveTaskButton != null) {
mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE);
@@ -477,44 +460,14 @@ public class TaskViewHeader extends FrameLayout
mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
mLightDismissDrawable : mDarkDismissDrawable);
- mDismissButton.setContentDescription(t.dismissDescription);
+ mDismissButton.setContentDescription(String.format(mDismissDescFormat, t.titleDescription));
mDismissButton.setOnClickListener(this);
mDismissButton.setClickable(false);
((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true);
- // When freeform workspaces are enabled, then update the move-task button depending on the
- // current task
- if (mMoveTaskButton != null) {
- if (t.isFreeformTask()) {
- mTaskWindowingMode = WINDOWING_MODE_FULLSCREEN;
- mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
- ? mLightFullscreenIcon
- : mDarkFullscreenIcon);
- } else {
- mTaskWindowingMode = WINDOWING_MODE_FREEFORM;
- mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
- ? mLightFreeformIcon
- : mDarkFreeformIcon);
- }
- mMoveTaskButton.setOnClickListener(this);
- mMoveTaskButton.setClickable(false);
- ((RippleDrawable) mMoveTaskButton.getBackground()).setForceSoftware(true);
- }
-
- if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) {
- if (mFocusTimerIndicator == null) {
- mFocusTimerIndicator = (ProgressBar) Utilities.findViewStubById(this,
- R.id.focus_timer_indicator_stub).inflate();
- }
- mFocusTimerIndicator.getProgressDrawable()
- .setColorFilter(
- getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
- PorterDuff.Mode.SRC_IN);
- }
-
// In accessibility, a single click on the focused app info button will show it
if (touchExplorationEnabled) {
- mIconView.setContentDescription(t.appInfoDescription);
+ mIconView.setContentDescription(String.format(mAppInfoDescFormat, t.titleDescription));
mIconView.setOnClickListener(this);
mIconView.setClickable(true);
}
@@ -651,7 +604,7 @@ public class TaskViewHeader extends FrameLayout
SystemServicesProxy ssp = Recents.getSystemServices();
ComponentName cn = mTask.key.getComponent();
int userId = mTask.key.userId;
- ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId);
+ ActivityInfo activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, userId);
if (activityInfo == null) {
return;
}
@@ -671,11 +624,12 @@ public class TaskViewHeader extends FrameLayout
}
// Update the overlay contents for the current app
- mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
+ mAppTitleView.setText(ActivityManagerWrapper.getInstance().getBadgedApplicationLabel(
+ activityInfo.applicationInfo, userId));
mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
- mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo,
- userId));
+ mAppIconView.setImageDrawable(ActivityManagerWrapper.getInstance().getBadgedApplicationIcon(
+ activityInfo.applicationInfo, userId));
mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
? mLightInfoIcon
: mDarkInfoIcon);
diff --git a/com/android/systemui/recents/views/TaskViewThumbnail.java b/com/android/systemui/recents/views/TaskViewThumbnail.java
index a2190b3a..4152b05a 100644
--- a/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -37,9 +37,9 @@ import android.view.ViewDebug;
import com.android.systemui.R;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import java.io.PrintWriter;
@@ -245,10 +245,6 @@ public class TaskViewThumbnail extends View {
public void updateThumbnailMatrix() {
mThumbnailScale = 1f;
if (mBitmapShader != null && mThumbnailData != null) {
- // We consider this a stack task if it is not freeform (ie. has no bounds) or has been
- // dragged into the stack from the freeform workspace
- boolean isStackTask = !mTask.isFreeformTask() || mTask.bounds == null;
- int xOffset, yOffset = 0;
if (mTaskViewRect.isEmpty()) {
// If we haven't measured , skip the thumbnail drawing and only draw the background
// color
@@ -266,7 +262,7 @@ public class TaskViewThumbnail extends View {
mThumbnailScale = (float) (mTaskViewRect.height() - mTitleBarHeight)
/ (float) mThumbnailRect.height();
}
- } else if (isStackTask) {
+ } else {
float invThumbnailScale = 1f / mFullscreenThumbnailScale;
if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) {
if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -283,12 +279,6 @@ public class TaskViewThumbnail extends View {
// Otherwise, scale the screenshot to fit 1:1 in the current orientation
mThumbnailScale = invThumbnailScale;
}
- } else {
- // Otherwise, if this is a freeform task with task bounds, then scale the thumbnail
- // to fit the entire bitmap into the task bounds
- mThumbnailScale = Math.min(
- (float) mTaskViewRect.width() / mThumbnailRect.width(),
- (float) mTaskViewRect.height() / mThumbnailRect.height());
}
mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale,
-mThumbnailData.insets.top * mFullscreenThumbnailScale);
diff --git a/com/android/systemui/recents/views/TaskViewTransform.java b/com/android/systemui/recents/views/TaskViewTransform.java
index 397f24eb..9b717e0e 100644
--- a/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/com/android/systemui/recents/views/TaskViewTransform.java
@@ -21,11 +21,11 @@ import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.util.IntProperty;
import android.util.Property;
import android.view.View;
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
import java.util.ArrayList;
@@ -59,7 +59,7 @@ public class TaskViewTransform {
public boolean visible = false;
- // This is a window-space rect used for positioning the task in the stack and freeform workspace
+ // This is a window-space rect used for positioning the task in the stack
public RectF rect = new RectF();
/**
diff --git a/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
index c5132024..ccda4b5a 100644
--- a/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
+++ b/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
@@ -25,10 +25,9 @@ import android.graphics.Rect;
import android.view.WindowManager;
import com.android.systemui.R;
-import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskViewTransform;
diff --git a/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java b/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
index 86ed583b..95f1d583 100644
--- a/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
+++ b/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
@@ -23,7 +23,7 @@ import android.view.View;
import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
import com.android.systemui.R;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.TaskStackView;
public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListener {
diff --git a/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java b/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
index 17e6b9e3..49cac269 100644
--- a/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
+++ b/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
@@ -23,8 +23,8 @@ import android.view.ViewConfiguration;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivityLaunchState;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskViewTransform;
diff --git a/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java b/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java
new file mode 100644
index 00000000..ddd27b0b
--- /dev/null
+++ b/com/android/systemui/shared/recents/model/BackgroundTaskLoader.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 com.android.systemui.shared.recents.model;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+/**
+ * Background task resource loader
+ */
+class BackgroundTaskLoader implements Runnable {
+ static String TAG = "BackgroundTaskLoader";
+ static boolean DEBUG = false;
+
+ private Context mContext;
+ private final HandlerThread mLoadThread;
+ private final Handler mLoadThreadHandler;
+ private final Handler mMainThreadHandler;
+
+ private final TaskResourceLoadQueue mLoadQueue;
+ private final TaskKeyLruCache<Drawable> mIconCache;
+ private final BitmapDrawable mDefaultIcon;
+
+ private boolean mStarted;
+ private boolean mCancelled;
+ private boolean mWaitingOnLoadQueue;
+
+ private final OnIdleChangedListener mOnIdleChangedListener;
+
+ /** Constructor, creates a new loading thread that loads task resources in the background */
+ public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
+ TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon,
+ OnIdleChangedListener onIdleChangedListener) {
+ mLoadQueue = loadQueue;
+ mIconCache = iconCache;
+ mDefaultIcon = defaultIcon;
+ mMainThreadHandler = new Handler();
+ mOnIdleChangedListener = onIdleChangedListener;
+ mLoadThread = new HandlerThread("Recents-TaskResourceLoader",
+ android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ mLoadThread.start();
+ mLoadThreadHandler = new Handler(mLoadThread.getLooper());
+ }
+
+ /** Restarts the loader thread */
+ void start(Context context) {
+ mContext = context;
+ mCancelled = false;
+ if (!mStarted) {
+ // Start loading on the load thread
+ mStarted = true;
+ mLoadThreadHandler.post(this);
+ } else {
+ // Notify the load thread to start loading again
+ synchronized (mLoadThread) {
+ mLoadThread.notifyAll();
+ }
+ }
+ }
+
+ /** Requests the loader thread to stop after the current iteration */
+ void stop() {
+ // Mark as cancelled for the thread to pick up
+ mCancelled = true;
+ // If we are waiting for the load queue for more tasks, then we can just reset the
+ // Context now, since nothing is using it
+ if (mWaitingOnLoadQueue) {
+ mContext = null;
+ }
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ if (mCancelled) {
+ // We have to unset the context here, since the background thread may be using it
+ // when we call stop()
+ mContext = null;
+ // If we are cancelled, then wait until we are started again
+ synchronized(mLoadThread) {
+ try {
+ mLoadThread.wait();
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ } else {
+ // If we've stopped the loader, then fall through to the above logic to wait on
+ // the load thread
+ processLoadQueueItem();
+
+ // If there are no other items in the list, then just wait until something is added
+ if (!mCancelled && mLoadQueue.isEmpty()) {
+ synchronized(mLoadQueue) {
+ try {
+ mWaitingOnLoadQueue = true;
+ mMainThreadHandler.post(
+ () -> mOnIdleChangedListener.onIdleChanged(true));
+ mLoadQueue.wait();
+ mMainThreadHandler.post(
+ () -> mOnIdleChangedListener.onIdleChanged(false));
+ mWaitingOnLoadQueue = false;
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This needs to be in a separate method to work around an surprising interpreter behavior:
+ * The register will keep the local reference to cachedThumbnailData even if it falls out of
+ * scope. Putting it into a method fixes this issue.
+ */
+ private void processLoadQueueItem() {
+ // Load the next item from the queue
+ final Task t = mLoadQueue.nextTask();
+ if (t != null) {
+ Drawable cachedIcon = mIconCache.get(t.key);
+
+ // Load the icon if it is stale or we haven't cached one yet
+ if (cachedIcon == null) {
+ cachedIcon = ActivityManagerWrapper.getInstance().getBadgedTaskDescriptionIcon(
+ mContext, t.taskDescription, t.key.userId, mContext.getResources());
+
+ if (cachedIcon == null) {
+ ActivityInfo info = PackageManagerWrapper.getInstance().getActivityInfo(
+ t.key.getComponent(), t.key.userId);
+ if (info != null) {
+ if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
+ cachedIcon = ActivityManagerWrapper.getInstance().getBadgedActivityIcon(
+ info, t.key.userId);
+ }
+ }
+
+ if (cachedIcon == null) {
+ cachedIcon = mDefaultIcon;
+ }
+
+ // At this point, even if we can't load the icon, we will set the
+ // default icon.
+ mIconCache.put(t.key, cachedIcon);
+ }
+
+ if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
+ final ThumbnailData thumbnailData =
+ ActivityManagerWrapper.getInstance().getTaskThumbnail(t.key.id,
+ true /* reducedResolution */);
+
+ if (!mCancelled) {
+ // Notify that the task data has changed
+ final Drawable finalIcon = cachedIcon;
+ mMainThreadHandler.post(
+ () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon));
+ }
+ }
+ }
+
+ interface OnIdleChangedListener {
+ void onIdleChanged(boolean idle);
+ }
+}
diff --git a/com/android/systemui/shared/recents/model/FilteredTaskList.java b/com/android/systemui/shared/recents/model/FilteredTaskList.java
new file mode 100644
index 00000000..898d64a1
--- /dev/null
+++ b/com/android/systemui/shared/recents/model/FilteredTaskList.java
@@ -0,0 +1,124 @@
+/*
+ * 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.shared.recents.model;
+
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A list of filtered tasks.
+ */
+class FilteredTaskList {
+
+ private final ArrayList<Task> mTasks = new ArrayList<>();
+ private final ArrayList<Task> mFilteredTasks = new ArrayList<>();
+ private final ArrayMap<TaskKey, Integer> mFilteredTaskIndices = new ArrayMap<>();
+ private TaskFilter mFilter;
+
+ /** Sets the task filter, and returns whether the set of filtered tasks have changed. */
+ boolean setFilter(TaskFilter filter) {
+ ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks);
+ mFilter = filter;
+ updateFilteredTasks();
+ return !prevFilteredTasks.equals(mFilteredTasks);
+ }
+
+ /** Adds a new task to the task list */
+ void add(Task t) {
+ mTasks.add(t);
+ updateFilteredTasks();
+ }
+
+ /** Sets the list of tasks */
+ void set(List<Task> tasks) {
+ mTasks.clear();
+ mTasks.addAll(tasks);
+ updateFilteredTasks();
+ }
+
+ /** Removes a task from the base list only if it is in the filtered list */
+ boolean remove(Task t) {
+ if (mFilteredTasks.contains(t)) {
+ boolean removed = mTasks.remove(t);
+ updateFilteredTasks();
+ return removed;
+ }
+ return false;
+ }
+
+ /** Returns the index of this task in the list of filtered tasks */
+ int indexOf(Task t) {
+ if (t != null && mFilteredTaskIndices.containsKey(t.key)) {
+ return mFilteredTaskIndices.get(t.key);
+ }
+ return -1;
+ }
+
+ /** Returns the size of the list of filtered tasks */
+ int size() {
+ return mFilteredTasks.size();
+ }
+
+ /** Returns whether the filtered list contains this task */
+ boolean contains(Task t) {
+ return mFilteredTaskIndices.containsKey(t.key);
+ }
+
+ /** Updates the list of filtered tasks whenever the base task list changes */
+ private void updateFilteredTasks() {
+ mFilteredTasks.clear();
+ if (mFilter != null) {
+ // Create a sparse array from task id to Task
+ SparseArray<Task> taskIdMap = new SparseArray<>();
+ int taskCount = mTasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task t = mTasks.get(i);
+ taskIdMap.put(t.key.id, t);
+ }
+
+ for (int i = 0; i < taskCount; i++) {
+ Task t = mTasks.get(i);
+ if (mFilter.acceptTask(taskIdMap, t, i)) {
+ mFilteredTasks.add(t);
+ }
+ }
+ } else {
+ mFilteredTasks.addAll(mTasks);
+ }
+ updateFilteredTaskIndices();
+ }
+
+ /** Updates the mapping of tasks to indices. */
+ private void updateFilteredTaskIndices() {
+ int taskCount = mFilteredTasks.size();
+ mFilteredTaskIndices.clear();
+ for (int i = 0; i < taskCount; i++) {
+ Task t = mFilteredTasks.get(i);
+ mFilteredTaskIndices.put(t.key, i);
+ }
+ }
+
+ /** Returns the list of filtered tasks */
+ ArrayList<Task> getTasks() {
+ return mFilteredTasks;
+ }
+}
diff --git a/com/android/systemui/recents/model/HighResThumbnailLoader.java b/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java
index 6414ea1e..24ba9984 100644
--- a/com/android/systemui/recents/model/HighResThumbnailLoader.java
+++ b/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.systemui.recents.model;
+package com.android.systemui.shared.recents.model;
import static android.os.Process.setThreadPriority;
@@ -25,10 +25,8 @@ import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task.TaskCallbacks;
+import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -38,6 +36,8 @@ import java.util.ArrayList;
*/
public class HighResThumbnailLoader implements TaskCallbacks {
+ private final ActivityManagerWrapper mActivityManager;
+
@GuardedBy("mLoadQueue")
private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>();
@GuardedBy("mLoadQueue")
@@ -46,20 +46,21 @@ public class HighResThumbnailLoader implements TaskCallbacks {
private boolean mLoaderIdling;
private final ArrayList<Task> mVisibleTasks = new ArrayList<>();
+
private final Thread mLoadThread;
private final Handler mMainThreadHandler;
- private final SystemServicesProxy mSystemServicesProxy;
private final boolean mIsLowRamDevice;
private boolean mLoading;
private boolean mVisible;
private boolean mFlingingFast;
private boolean mTaskLoadQueueIdle;
- public HighResThumbnailLoader(SystemServicesProxy ssp, Looper looper, boolean isLowRamDevice) {
+ public HighResThumbnailLoader(ActivityManagerWrapper activityManager, Looper looper,
+ boolean isLowRamDevice) {
+ mActivityManager = activityManager;
mMainThreadHandler = new Handler(looper);
mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader");
mLoadThread.start();
- mSystemServicesProxy = ssp;
mIsLowRamDevice = isLowRamDevice;
}
@@ -220,7 +221,7 @@ public class HighResThumbnailLoader implements TaskCallbacks {
}
private void loadTask(Task t) {
- ThumbnailData thumbnail = mSystemServicesProxy.getTaskThumbnail(t.key.id,
+ ThumbnailData thumbnail = mActivityManager.getTaskThumbnail(t.key.id,
false /* reducedResolution */);
mMainThreadHandler.post(() -> {
synchronized (mLoadQueue) {
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
new file mode 100644
index 00000000..c9368f3e
--- /dev/null
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
@@ -0,0 +1,205 @@
+/*
+ * 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.systemui.shared.recents.model;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.SparseBooleanArray;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * This class stores the loading state as it goes through multiple stages of loading:
+ * 1) preloadRawTasks() will load the raw set of recents tasks from the system
+ * 2) preloadPlan() will construct a new task stack with all metadata and only icons and
+ * thumbnails that are currently in the cache
+ * 3) executePlan() will actually load and fill in the icons and thumbnails according to the load
+ * options specified, such that we can transition into the Recents activity seamlessly
+ */
+public class RecentsTaskLoadPlan {
+
+ /** The set of conditions to load tasks. */
+ public static class Options {
+ public int runningTaskId = -1;
+ public boolean loadIcons = true;
+ public boolean loadThumbnails = false;
+ public boolean onlyLoadForCache = false;
+ public boolean onlyLoadPausedActivities = false;
+ public int numVisibleTasks = 0;
+ public int numVisibleTaskThumbnails = 0;
+ }
+
+ private final Context mContext;
+ private final KeyguardManager mKeyguardManager;
+
+ private List<ActivityManager.RecentTaskInfo> mRawTasks;
+ private TaskStack mStack;
+
+ private final SparseBooleanArray mTmpLockedUsers = new SparseBooleanArray();
+
+ public RecentsTaskLoadPlan(Context context) {
+ mContext = context;
+ mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+ }
+
+ /**
+ * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
+ * to most-recent order.
+ *
+ * Note: Do not lock, callers should synchronize on the loader before making this call.
+ */
+ void preloadRawTasks() {
+ int currentUserId = ActivityManagerWrapper.getInstance().getCurrentUserId();
+ mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
+ ActivityManager.getMaxRecentTasksStatic(), currentUserId);
+
+ // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
+ Collections.reverse(mRawTasks);
+ }
+
+ /**
+ * Preloads the list of recent tasks from the system. After this call, the TaskStack will
+ * have a list of all the recent tasks with their metadata, not including icons or
+ * thumbnails which were not cached and have to be loaded.
+ *
+ * The tasks will be ordered by:
+ * - least-recent to most-recent stack tasks
+ *
+ * Note: Do not lock, since this can be calling back to the loader, which separately also drives
+ * this call (callers should synchronize on the loader before making this call).
+ */
+ void preloadPlan(RecentsTaskLoader loader, int runningTaskId) {
+ Resources res = mContext.getResources();
+ ArrayList<Task> allTasks = new ArrayList<>();
+ if (mRawTasks == null) {
+ preloadRawTasks();
+ }
+
+ int taskCount = mRawTasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
+
+ // Compose the task key
+ final int windowingMode = t.configuration.windowConfiguration.getWindowingMode();
+ TaskKey taskKey = new TaskKey(t.persistentId, windowingMode, t.baseIntent,
+ t.userId, t.lastActiveTime);
+
+ boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM;
+ boolean isStackTask = !isFreeformTask;
+ boolean isLaunchTarget = taskKey.id == runningTaskId;
+
+ // Load the title, icon, and color
+ ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
+ String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
+ String titleDescription = loader.getAndUpdateContentDescription(taskKey,
+ t.taskDescription);
+ Drawable icon = isStackTask
+ ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
+ : null;
+ ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
+ false /* loadIfNotCached */, false /* storeInCache */);
+ int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
+ int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
+ boolean isSystemApp = (info != null) &&
+ ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+
+ // TODO: Refactor to not do this every preload
+ if (mTmpLockedUsers.indexOfKey(t.userId) < 0) {
+ mTmpLockedUsers.put(t.userId, mKeyguardManager.isDeviceLocked(t.userId));
+ }
+ boolean isLocked = mTmpLockedUsers.get(t.userId);
+
+ // Add the task to the stack
+ Task task = new Task(taskKey, icon,
+ thumbnail, title, titleDescription, activityColor, backgroundColor,
+ isLaunchTarget, isStackTask, isSystemApp, t.supportsSplitScreenMultiWindow,
+ t.taskDescription, t.resizeMode, t.topActivity, isLocked);
+
+ allTasks.add(task);
+ }
+
+ // Initialize the stacks
+ mStack = new TaskStack();
+ mStack.setTasks(allTasks, false /* notifyStackChanges */);
+ }
+
+ /**
+ * Called to apply the actual loading based on the specified conditions.
+ *
+ * Note: Do not lock, since this can be calling back to the loader, which separately also drives
+ * this call (callers should synchronize on the loader before making this call).
+ */
+ void executePlan(Options opts, RecentsTaskLoader loader) {
+ Resources res = mContext.getResources();
+
+ // Iterate through each of the tasks and load them according to the load conditions.
+ ArrayList<Task> tasks = mStack.getStackTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ TaskKey taskKey = task.key;
+
+ boolean isRunningTask = (task.key.id == opts.runningTaskId);
+ boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
+ boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
+
+ // If requested, skip the running task
+ if (opts.onlyLoadPausedActivities && isRunningTask) {
+ continue;
+ }
+
+ if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
+ if (task.icon == null) {
+ task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res,
+ true);
+ }
+ }
+ if (opts.loadThumbnails && isVisibleThumbnail) {
+ task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
+ true /* loadIfNotCached */, true /* storeInCache */);
+ }
+ }
+ }
+
+ /**
+ * Returns the TaskStack from the preloaded list of recent tasks.
+ */
+ public TaskStack getTaskStack() {
+ return mStack;
+ }
+
+ /** Returns whether there are any tasks in any stacks. */
+ public boolean hasTasks() {
+ if (mStack != null) {
+ return mStack.getTaskCount() > 0;
+ }
+ return false;
+ }
+}
diff --git a/com/android/systemui/recents/model/RecentsTaskLoader.java b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
index 1b893862..de4c72c2 100644
--- a/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.recents.model;
+package com.android.systemui.shared.recents.model;
import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
@@ -25,238 +25,47 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.HandlerThread;
import android.os.Looper;
import android.os.Trace;
import android.util.Log;
import android.util.LruCache;
import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
import java.io.PrintWriter;
import java.util.Map;
-import java.util.concurrent.ConcurrentLinkedQueue;
/**
- * A Task load queue
- */
-class TaskResourceLoadQueue {
-
- ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
-
- /** Adds a new task to the load queue */
- void addTask(Task t) {
- if (!mQueue.contains(t)) {
- mQueue.add(t);
- }
- synchronized(this) {
- notifyAll();
- }
- }
-
- /**
- * Retrieves the next task from the load queue, as well as whether we want that task to be
- * force reloaded.
- */
- Task nextTask() {
- return mQueue.poll();
- }
-
- /** Removes a task from the load queue */
- void removeTask(Task t) {
- mQueue.remove(t);
- }
-
- /** Clears all the tasks from the load queue */
- void clearTasks() {
- mQueue.clear();
- }
-
- /** Returns whether the load queue is empty */
- boolean isEmpty() {
- return mQueue.isEmpty();
- }
-}
-
-/**
- * Task resource loader
- */
-class BackgroundTaskLoader implements Runnable {
- static String TAG = "TaskResourceLoader";
- static boolean DEBUG = false;
-
- Context mContext;
- HandlerThread mLoadThread;
- Handler mLoadThreadHandler;
- Handler mMainThreadHandler;
-
- TaskResourceLoadQueue mLoadQueue;
- TaskKeyLruCache<Drawable> mIconCache;
- BitmapDrawable mDefaultIcon;
-
- boolean mStarted;
- boolean mCancelled;
- boolean mWaitingOnLoadQueue;
-
- private final OnIdleChangedListener mOnIdleChangedListener;
-
- /** Constructor, creates a new loading thread that loads task resources in the background */
- public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
- TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon,
- OnIdleChangedListener onIdleChangedListener) {
- mLoadQueue = loadQueue;
- mIconCache = iconCache;
- mDefaultIcon = defaultIcon;
- mMainThreadHandler = new Handler();
- mOnIdleChangedListener = onIdleChangedListener;
- mLoadThread = new HandlerThread("Recents-TaskResourceLoader",
- android.os.Process.THREAD_PRIORITY_BACKGROUND);
- mLoadThread.start();
- mLoadThreadHandler = new Handler(mLoadThread.getLooper());
- }
-
- /** Restarts the loader thread */
- void start(Context context) {
- mContext = context;
- mCancelled = false;
- if (!mStarted) {
- // Start loading on the load thread
- mStarted = true;
- mLoadThreadHandler.post(this);
- } else {
- // Notify the load thread to start loading again
- synchronized (mLoadThread) {
- mLoadThread.notifyAll();
- }
- }
- }
-
- /** Requests the loader thread to stop after the current iteration */
- void stop() {
- // Mark as cancelled for the thread to pick up
- mCancelled = true;
- // If we are waiting for the load queue for more tasks, then we can just reset the
- // Context now, since nothing is using it
- if (mWaitingOnLoadQueue) {
- mContext = null;
- }
- }
-
- @Override
- public void run() {
- while (true) {
- if (mCancelled) {
- // We have to unset the context here, since the background thread may be using it
- // when we call stop()
- mContext = null;
- // If we are cancelled, then wait until we are started again
- synchronized(mLoadThread) {
- try {
- mLoadThread.wait();
- } catch (InterruptedException ie) {
- ie.printStackTrace();
- }
- }
- } else {
- SystemServicesProxy ssp = Recents.getSystemServices();
- // If we've stopped the loader, then fall through to the above logic to wait on
- // the load thread
- if (ssp != null) {
- processLoadQueueItem(ssp);
- }
-
- // If there are no other items in the list, then just wait until something is added
- if (!mCancelled && mLoadQueue.isEmpty()) {
- synchronized(mLoadQueue) {
- try {
- mWaitingOnLoadQueue = true;
- mMainThreadHandler.post(
- () -> mOnIdleChangedListener.onIdleChanged(true));
- mLoadQueue.wait();
- mMainThreadHandler.post(
- () -> mOnIdleChangedListener.onIdleChanged(false));
- mWaitingOnLoadQueue = false;
- } catch (InterruptedException ie) {
- ie.printStackTrace();
- }
- }
- }
- }
- }
- }
-
- /**
- * This needs to be in a separate method to work around an surprising interpreter behavior:
- * The register will keep the local reference to cachedThumbnailData even if it falls out of
- * scope. Putting it into a method fixes this issue.
- */
- private void processLoadQueueItem(SystemServicesProxy ssp) {
- // Load the next item from the queue
- final Task t = mLoadQueue.nextTask();
- if (t != null) {
- Drawable cachedIcon = mIconCache.get(t.key);
-
- // Load the icon if it is stale or we haven't cached one yet
- if (cachedIcon == null) {
- cachedIcon = ssp.getBadgedTaskDescriptionIcon(t.taskDescription,
- t.key.userId, mContext.getResources());
-
- if (cachedIcon == null) {
- ActivityInfo info = ssp.getActivityInfo(
- t.key.getComponent(), t.key.userId);
- if (info != null) {
- if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
- cachedIcon = ssp.getBadgedActivityIcon(info, t.key.userId);
- }
- }
-
- if (cachedIcon == null) {
- cachedIcon = mDefaultIcon;
- }
-
- // At this point, even if we can't load the icon, we will set the
- // default icon.
- mIconCache.put(t.key, cachedIcon);
- }
-
- if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
- final ThumbnailData thumbnailData = ssp.getTaskThumbnail(t.key.id,
- true /* reducedResolution */);
-
- if (!mCancelled) {
- // Notify that the task data has changed
- final Drawable finalIcon = cachedIcon;
- mMainThreadHandler.post(
- () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon));
- }
- }
- }
-
- interface OnIdleChangedListener {
- void onIdleChanged(boolean idle);
- }
-}
-
-/**
* Recents task loader
*/
public class RecentsTaskLoader {
-
private static final String TAG = "RecentsTaskLoader";
private static final boolean DEBUG = false;
+ /** Levels of svelte in increasing severity/austerity. */
+ // No svelting.
+ public static final int SVELTE_NONE = 0;
+ // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable
+ // caching thumbnails as you scroll.
+ public static final int SVELTE_LIMIT_CACHE = 1;
+ // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and
+ // evict all thumbnails when hidden.
+ public static final int SVELTE_DISABLE_CACHE = 2;
+ // Disable all thumbnail loading.
+ public static final int SVELTE_DISABLE_LOADING = 3;
+
+ private final Context mContext;
+
// This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
// for many tasks, which we use to get the activity labels and icons. Unlike the other caches
// below, this is per-package so we can't invalidate the items in the cache based on the last
- // active time. Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a
+ // active time. Instead, we rely on the PackageMonitor to keep us informed whenever a
// package in the cache has been updated, so that we may remove it.
private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
private final TaskKeyLruCache<Drawable> mIconCache;
@@ -272,31 +81,27 @@ public class RecentsTaskLoader {
private final int mMaxThumbnailCacheSize;
private final int mMaxIconCacheSize;
private int mNumVisibleTasksLoaded;
+ private int mSvelteLevel;
- int mDefaultTaskBarBackgroundColor;
- int mDefaultTaskViewBackgroundColor;
- BitmapDrawable mDefaultIcon;
+ private int mDefaultTaskBarBackgroundColor;
+ private int mDefaultTaskViewBackgroundColor;
+ private final BitmapDrawable mDefaultIcon;
- private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction =
- new TaskKeyLruCache.EvictionCallback() {
+ private EvictionCallback mClearActivityInfoOnEviction = new EvictionCallback() {
@Override
- public void onEntryEvicted(Task.TaskKey key) {
+ public void onEntryEvicted(TaskKey key) {
if (key != null) {
mActivityInfoCache.remove(key.getComponent());
}
}
};
- public RecentsTaskLoader(Context context) {
- Resources res = context.getResources();
- mDefaultTaskBarBackgroundColor =
- context.getColor(R.color.recents_task_bar_default_background_color);
- mDefaultTaskViewBackgroundColor =
- context.getColor(R.color.recents_task_view_default_background_color);
- mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
- mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
- int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
- mMaxIconCacheSize;
+ public RecentsTaskLoader(Context context, int maxThumbnailCacheSize, int maxIconCacheSize,
+ int svelteLevel) {
+ mContext = context;
+ mMaxThumbnailCacheSize = maxThumbnailCacheSize;
+ mMaxIconCacheSize = maxIconCacheSize;
+ mSvelteLevel = svelteLevel;
// Create the default assets
Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
@@ -305,18 +110,27 @@ public class RecentsTaskLoader {
// Initialize the proxy, cache and loaders
int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
- mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(),
- Looper.getMainLooper(), Recents.getConfiguration().isLowRamDevice);
+ mHighResThumbnailLoader = new HighResThumbnailLoader(ActivityManagerWrapper.getInstance(),
+ Looper.getMainLooper(), ActivityManager.isLowRamDeviceStatic());
mLoadQueue = new TaskResourceLoadQueue();
- mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction);
+ mIconCache = new TaskKeyLruCache<>(mMaxIconCacheSize, mClearActivityInfoOnEviction);
mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
mClearActivityInfoOnEviction);
- mActivityInfoCache = new LruCache(numRecentTasks);
+ mActivityInfoCache = new LruCache<>(numRecentTasks);
mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultIcon,
mHighResThumbnailLoader::setTaskLoadQueueIdle);
}
+ /**
+ * Sets the default task bar/view colors if none are provided by the app.
+ */
+ public void setDefaultColors(int defaultTaskBarBackgroundColor,
+ int defaultTaskViewBackgroundColor) {
+ mDefaultTaskBarBackgroundColor = defaultTaskBarBackgroundColor;
+ mDefaultTaskViewBackgroundColor = defaultTaskViewBackgroundColor;
+ }
+
/** Returns the size of the app icon cache. */
public int getIconCacheSize() {
return mMaxIconCacheSize;
@@ -331,37 +145,22 @@ public class RecentsTaskLoader {
return mHighResThumbnailLoader;
}
- /** Creates a new plan for loading the recent tasks. */
- public RecentsTaskLoadPlan createLoadPlan(Context context) {
- RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
- return plan;
- }
-
- /** Preloads raw recents tasks using the specified plan to store the output. */
- public synchronized void preloadRawTasks(RecentsTaskLoadPlan plan,
- boolean includeFrontMostExcludedTask) {
- plan.preloadRawTasks(includeFrontMostExcludedTask);
- }
-
/** Preloads recents tasks using the specified plan to store the output. */
- public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
- boolean includeFrontMostExcludedTask) {
+ public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) {
try {
Trace.beginSection("preloadPlan");
- plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
+ plan.preloadPlan(this, runningTaskId);
} finally {
Trace.endSection();
}
}
/** Begins loading the heavy task data according to the specified options. */
- public synchronized void loadTasks(Context context, RecentsTaskLoadPlan plan,
- RecentsTaskLoadPlan.Options opts) {
+ public synchronized void loadTasks(RecentsTaskLoadPlan plan, Options opts) {
if (opts == null) {
throw new RuntimeException("Requires load options");
}
if (opts.onlyLoadForCache && opts.loadThumbnails) {
-
// If we are loading for the cache, we'd like to have the real cache only include the
// visible thumbnails. However, we also don't want to reload already cached thumbnails.
// Thus, we copy over the current entries into a second cache, and clear the real cache,
@@ -444,12 +243,25 @@ public class RecentsTaskLoader {
}
}
+ public void onPackageChanged(String packageName) {
+ // Remove all the cached activity infos for this package. The other caches do not need to
+ // be pruned at this time, as the TaskKey expiration checks will flush them next time their
+ // cached contents are requested
+ Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
+ for (ComponentName cn : activityInfoCache.keySet()) {
+ if (cn.getPackageName().equals(packageName)) {
+ if (DEBUG) {
+ Log.d(TAG, "Removing activity info from cache: " + cn);
+ }
+ mActivityInfoCache.remove(cn);
+ }
+ }
+ }
+
/**
* Returns the cached task label if the task key is not expired, updating the cache if it is.
*/
- String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) {
- SystemServicesProxy ssp = Recents.getSystemServices();
-
+ String getAndUpdateActivityTitle(TaskKey taskKey, ActivityManager.TaskDescription td) {
// Return the task description label if it exists
if (td != null && td.getLabel() != null) {
return td.getLabel();
@@ -462,7 +274,8 @@ public class RecentsTaskLoader {
// All short paths failed, load the label from the activity info and cache it
ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
if (activityInfo != null) {
- label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId);
+ label = ActivityManagerWrapper.getInstance().getBadgedActivityLabel(activityInfo,
+ taskKey.userId);
mActivityLabelCache.put(taskKey, label);
return label;
}
@@ -475,10 +288,7 @@ public class RecentsTaskLoader {
* Returns the cached task content description if the task key is not expired, updating the
* cache if it is.
*/
- String getAndUpdateContentDescription(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
- Resources res) {
- SystemServicesProxy ssp = Recents.getSystemServices();
-
+ String getAndUpdateContentDescription(TaskKey taskKey, ActivityManager.TaskDescription td) {
// Return the cached content description if it exists
String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
if (label != null) {
@@ -488,7 +298,8 @@ public class RecentsTaskLoader {
// All short paths failed, load the label from the activity info and cache it
ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
if (activityInfo != null) {
- label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, td, res);
+ label = ActivityManagerWrapper.getInstance().getBadgedContentDescription(
+ activityInfo, taskKey.userId, td);
if (td == null) {
// Only add to the cache if the task description is null, otherwise, it is possible
// for the task description to change between calls without the last active time
@@ -507,10 +318,8 @@ public class RecentsTaskLoader {
/**
* Returns the cached task icon if the task key is not expired, updating the cache if it is.
*/
- Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
+ Drawable getAndUpdateActivityIcon(TaskKey taskKey, ActivityManager.TaskDescription td,
Resources res, boolean loadIfNotCached) {
- SystemServicesProxy ssp = Recents.getSystemServices();
-
// Return the cached activity icon if it exists
Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey);
if (icon != null) {
@@ -519,7 +328,8 @@ public class RecentsTaskLoader {
if (loadIfNotCached) {
// Return and cache the task description icon if it exists
- icon = ssp.getBadgedTaskDescriptionIcon(td, taskKey.userId, res);
+ icon = ActivityManagerWrapper.getInstance().getBadgedTaskDescriptionIcon(mContext, td,
+ taskKey.userId, res);
if (icon != null) {
mIconCache.put(taskKey, icon);
return icon;
@@ -528,7 +338,8 @@ public class RecentsTaskLoader {
// Load the icon from the activity info and cache it
ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
if (activityInfo != null) {
- icon = ssp.getBadgedActivityIcon(activityInfo, taskKey.userId);
+ icon = ActivityManagerWrapper.getInstance().getBadgedActivityIcon(activityInfo,
+ taskKey.userId);
if (icon != null) {
mIconCache.put(taskKey, icon);
return icon;
@@ -542,10 +353,8 @@ public class RecentsTaskLoader {
/**
* Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
*/
- synchronized ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached,
+ synchronized ThumbnailData getAndUpdateThumbnail(TaskKey taskKey, boolean loadIfNotCached,
boolean storeInCache) {
- SystemServicesProxy ssp = Recents.getSystemServices();
-
ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey);
if (cached != null) {
return cached;
@@ -558,11 +367,10 @@ public class RecentsTaskLoader {
}
if (loadIfNotCached) {
- RecentsConfiguration config = Recents.getConfiguration();
- if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
+ if (mSvelteLevel < SVELTE_DISABLE_LOADING) {
// Load the thumbnail from the system
- ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id,
- true /* reducedResolution */);
+ ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance().getTaskThumbnail(
+ taskKey.id, true /* reducedResolution */);
if (thumbnailData.thumbnail != null) {
if (storeInCache) {
mThumbnailCache.put(taskKey, thumbnailData);
@@ -601,12 +409,11 @@ public class RecentsTaskLoader {
* Returns the activity info for the given task key, retrieving one from the system if the
* task key is expired.
*/
- ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
- SystemServicesProxy ssp = Recents.getSystemServices();
+ ActivityInfo getAndUpdateActivityInfo(TaskKey taskKey) {
ComponentName cn = taskKey.getComponent();
ActivityInfo activityInfo = mActivityInfoCache.get(cn);
if (activityInfo == null) {
- activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
+ activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, taskKey.userId);
if (cn == null || activityInfo == null) {
Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " +
activityInfo);
@@ -632,23 +439,6 @@ public class RecentsTaskLoader {
mLoadQueue.clearTasks();
}
- /**** Event Bus Events ****/
-
- public final void onBusEvent(PackagesChangedEvent event) {
- // Remove all the cached activity infos for this package. The other caches do not need to
- // be pruned at this time, as the TaskKey expiration checks will flush them next time their
- // cached contents are requested
- Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
- for (ComponentName cn : activityInfoCache.keySet()) {
- if (cn.getPackageName().equals(event.packageName)) {
- if (DEBUG) {
- Log.d(TAG, "Removing activity info from cache: " + cn);
- }
- mActivityInfoCache.remove(cn);
- }
- }
- }
-
public synchronized void dump(String prefix, PrintWriter writer) {
String innerPrefix = prefix + " ";
diff --git a/com/android/systemui/recents/model/Task.java b/com/android/systemui/shared/recents/model/Task.java
index abdb5cb8..6bddbe01 100644
--- a/com/android/systemui/recents/model/Task.java
+++ b/com/android/systemui/shared/recents/model/Task.java
@@ -14,22 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.recents.model;
+package com.android.systemui.shared.recents.model;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-
-import android.app.ActivityManager;
+import android.app.ActivityManager.TaskDescription;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.ViewDebug;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.Utilities;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -45,11 +40,11 @@ public class Task {
/* Task callbacks */
public interface TaskCallbacks {
/* Notifies when a task has been bound */
- public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
+ void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
/* Notifies when a task has been unbound */
- public void onTaskDataUnloaded();
+ void onTaskDataUnloaded();
/* Notifies when a task's windowing mode has changed. */
- public void onTaskWindowingModeChanged();
+ void onTaskWindowingModeChanged();
}
/* The Task Key represents the unique primary key for the task */
@@ -63,19 +58,15 @@ public class Task {
@ViewDebug.ExportedProperty(category="recents")
public final int userId;
@ViewDebug.ExportedProperty(category="recents")
- public long firstActiveTime;
- @ViewDebug.ExportedProperty(category="recents")
public long lastActiveTime;
private int mHashCode;
- public TaskKey(int id, int windowingMode, Intent intent, int userId, long firstActiveTime,
- long lastActiveTime) {
+ public TaskKey(int id, int windowingMode, Intent intent, int userId, long lastActiveTime) {
this.id = id;
this.windowingMode = windowingMode;
this.baseIntent = intent;
this.userId = userId;
- this.firstActiveTime = firstActiveTime;
this.lastActiveTime = lastActiveTime;
updateHashCode();
}
@@ -125,20 +116,6 @@ public class Task {
public int temporarySortIndexInStack;
/**
- * The group will be computed separately from the initialization of the task
- */
- @ViewDebug.ExportedProperty(deepExport=true, prefix="group_")
- public TaskGrouping group;
- /**
- * The affiliationTaskId is the task id of the parent task or itself if it is not affiliated
- * with any task.
- */
- @ViewDebug.ExportedProperty(category="recents")
- public int affiliationTaskId;
- @ViewDebug.ExportedProperty(category="recents")
- public int affiliationColor;
-
- /**
* The icon is the task description icon (if provided), which falls back to the activity icon,
* which can then fall back to the application icon.
*/
@@ -149,10 +126,6 @@ public class Task {
@ViewDebug.ExportedProperty(category="recents")
public String titleDescription;
@ViewDebug.ExportedProperty(category="recents")
- public String dismissDescription;
- @ViewDebug.ExportedProperty(category="recents")
- public String appInfoDescription;
- @ViewDebug.ExportedProperty(category="recents")
public int colorPrimary;
@ViewDebug.ExportedProperty(category="recents")
public int colorBackground;
@@ -160,15 +133,9 @@ public class Task {
public boolean useLightOnPrimaryColor;
/**
- * The bounds of the task, used only if it is a freeform task.
- */
- @ViewDebug.ExportedProperty(category="recents")
- public Rect bounds;
-
- /**
* The task description for this task, only used to reload task icons.
*/
- public ActivityManager.TaskDescription taskDescription;
+ public TaskDescription taskDescription;
/**
* The state isLaunchTarget will be set for the correct task upon launching Recents.
@@ -200,28 +167,20 @@ public class Task {
// Do nothing
}
- public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
- ThumbnailData thumbnail, String title, String titleDescription,
- String dismissDescription, String appInfoDescription, int colorPrimary,
- int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
- boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
- int resizeMode, ComponentName topActivity, boolean isLocked) {
- boolean isInAffiliationGroup = (affiliationTaskId != key.id);
- boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
+ public Task(TaskKey key, Drawable icon, ThumbnailData thumbnail, String title,
+ String titleDescription, int colorPrimary, int colorBackground, boolean isLaunchTarget,
+ boolean isStackTask, boolean isSystemApp, boolean isDockable,
+ TaskDescription taskDescription, int resizeMode, ComponentName topActivity,
+ boolean isLocked) {
this.key = key;
- this.affiliationTaskId = affiliationTaskId;
- this.affiliationColor = affiliationColor;
this.icon = icon;
this.thumbnail = thumbnail;
this.title = title;
this.titleDescription = titleDescription;
- this.dismissDescription = dismissDescription;
- this.appInfoDescription = appInfoDescription;
- this.colorPrimary = hasAffiliationGroupColor ? affiliationColor : colorPrimary;
+ this.colorPrimary = colorPrimary;
this.colorBackground = colorBackground;
this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
Color.WHITE) > 3f;
- this.bounds = bounds;
this.taskDescription = taskDescription;
this.isLaunchTarget = isLaunchTarget;
this.isStackTask = isStackTask;
@@ -237,19 +196,13 @@ public class Task {
*/
public void copyFrom(Task o) {
this.key = o.key;
- this.group = o.group;
- this.affiliationTaskId = o.affiliationTaskId;
- this.affiliationColor = o.affiliationColor;
this.icon = o.icon;
this.thumbnail = o.thumbnail;
this.title = o.title;
this.titleDescription = o.titleDescription;
- this.dismissDescription = o.dismissDescription;
- this.appInfoDescription = o.appInfoDescription;
this.colorPrimary = o.colorPrimary;
this.colorBackground = o.colorBackground;
this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
- this.bounds = o.bounds;
this.taskDescription = o.taskDescription;
this.isLaunchTarget = o.isLaunchTarget;
this.isStackTask = o.isStackTask;
@@ -276,11 +229,6 @@ public class Task {
mCallbacks.remove(cb);
}
- /** Set the grouping */
- public void setGroup(TaskGrouping group) {
- this.group = group;
- }
-
/** Updates the task's windowing mode. */
public void setWindowingMode(int windowingMode) {
key.setWindowingMode(windowingMode);
@@ -290,14 +238,6 @@ public class Task {
}
}
- /**
- * Returns whether this task is on the freeform task stack.
- */
- public boolean isFreeformTask() {
- SystemServicesProxy ssp = Recents.getSystemServices();
- return ssp.hasFreeformWorkspaceSupport() && key.windowingMode == WINDOWING_MODE_FREEFORM;
- }
-
/** Notifies the callback listeners that this task has been loaded */
public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
this.icon = applicationIcon;
@@ -318,13 +258,6 @@ public class Task {
}
/**
- * Returns whether this task is affiliated with another task.
- */
- public boolean isAffiliatedTask() {
- return key.id != affiliationTaskId;
- }
-
- /**
* Returns the top activity component.
*/
public ComponentName getTopComponent() {
@@ -347,18 +280,12 @@ public class Task {
public void dump(String prefix, PrintWriter writer) {
writer.print(prefix); writer.print(key);
- if (isAffiliatedTask()) {
- writer.print(" "); writer.print("affTaskId=" + affiliationTaskId);
- }
if (!isDockable) {
writer.print(" dockable=N");
}
if (isLaunchTarget) {
writer.print(" launchTarget=Y");
}
- if (isFreeformTask()) {
- writer.print(" freeform=Y");
- }
if (isLocked) {
writer.print(" locked=Y");
}
diff --git a/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java b/com/android/systemui/shared/recents/model/TaskFilter.java
index fe3bf263..9a1ff544 100644
--- a/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
+++ b/com/android/systemui/shared/recents/model/TaskFilter.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.
@@ -11,16 +11,17 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
*/
-package com.android.systemui.recents.events.activity;
+package com.android.systemui.shared.recents.model;
-import com.android.systemui.recents.events.EventBus;
+import android.util.SparseArray;
/**
- * This is sent when the SystemUI tuner changes a flag.
+ * An interface for a task filter to query whether a particular task should show in a stack.
*/
-public class DebugFlagsChangedEvent extends EventBus.Event {
- // Simple event
+interface TaskFilter {
+ /** Returns whether the filter accepts the specified task */
+ boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index);
}
diff --git a/com/android/systemui/recents/model/TaskKeyCache.java b/com/android/systemui/shared/recents/model/TaskKeyCache.java
index 247a6542..4bf3500a 100644
--- a/com/android/systemui/recents/model/TaskKeyCache.java
+++ b/com/android/systemui/shared/recents/model/TaskKeyCache.java
@@ -14,12 +14,12 @@
* limitations under the License
*/
-package com.android.systemui.recents.model;
+package com.android.systemui.shared.recents.model;
import android.util.Log;
import android.util.SparseArray;
-import com.android.systemui.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
/**
* Base class for both strong and LRU task key cache.
@@ -34,7 +34,7 @@ public abstract class TaskKeyCache<V> {
* Gets a specific entry in the cache with the specified key, regardless of whether the cached
* value is valid or not.
*/
- final V get(Task.TaskKey key) {
+ final V get(TaskKey key) {
return getCacheEntry(key.id);
}
@@ -42,8 +42,8 @@ public abstract class TaskKeyCache<V> {
* Returns the value only if the key is valid (has not been updated since the last time it was
* in the cache)
*/
- final V getAndInvalidateIfModified(Task.TaskKey key) {
- Task.TaskKey lastKey = mKeys.get(key.id);
+ final V getAndInvalidateIfModified(TaskKey key) {
+ TaskKey lastKey = mKeys.get(key.id);
if (lastKey != null) {
if ((lastKey.windowingMode != key.windowingMode) ||
(lastKey.lastActiveTime != key.lastActiveTime)) {
@@ -59,7 +59,7 @@ public abstract class TaskKeyCache<V> {
}
/** Puts an entry in the cache for a specific key. */
- final void put(Task.TaskKey key, V value) {
+ final void put(TaskKey key, V value) {
if (key == null || value == null) {
Log.e(TAG, "Unexpected null key or value: " + key + ", " + value);
return;
@@ -70,7 +70,7 @@ public abstract class TaskKeyCache<V> {
/** Removes a cache entry for a specific key. */
- final void remove(Task.TaskKey key) {
+ final void remove(TaskKey key) {
// Remove the key after the cache value because we need it to make the callback
removeCacheEntry(key.id);
mKeys.remove(key.id);
diff --git a/com/android/systemui/recents/model/TaskKeyLruCache.java b/com/android/systemui/shared/recents/model/TaskKeyLruCache.java
index 778df6be..0ba2c3bf 100644
--- a/com/android/systemui/recents/model/TaskKeyLruCache.java
+++ b/com/android/systemui/shared/recents/model/TaskKeyLruCache.java
@@ -14,14 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.recents.model;
+package com.android.systemui.shared.recents.model;
import android.util.LruCache;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+
import java.io.PrintWriter;
/**
- * A mapping of {@link Task.TaskKey} to value, with additional LRU functionality where the least
+ * A mapping of {@link TaskKey} to value, with additional LRU functionality where the least
* recently referenced key/values will be evicted as more values than the given cache size are
* inserted.
*
@@ -31,7 +33,7 @@ import java.io.PrintWriter;
public class TaskKeyLruCache<V> extends TaskKeyCache<V> {
public interface EvictionCallback {
- public void onEntryEvicted(Task.TaskKey key);
+ void onEntryEvicted(TaskKey key);
}
private final LruCache<Integer, V> mCache;
diff --git a/com/android/systemui/recents/model/TaskKeyStrongCache.java b/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java
index c84df8a1..4408eced 100644
--- a/com/android/systemui/recents/model/TaskKeyStrongCache.java
+++ b/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java
@@ -14,13 +14,11 @@
* limitations under the License
*/
-package com.android.systemui.recents.model;
+package com.android.systemui.shared.recents.model;
import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
-import com.android.systemui.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
import java.io.PrintWriter;
diff --git a/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java b/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java
new file mode 100644
index 00000000..fbb6aceb
--- /dev/null
+++ b/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java
@@ -0,0 +1,60 @@
+/*
+ * 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.shared.recents.model;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * A Task load queue
+ */
+class TaskResourceLoadQueue {
+
+ private final ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<>();
+
+ /** Adds a new task to the load queue */
+ void addTask(Task t) {
+ if (!mQueue.contains(t)) {
+ mQueue.add(t);
+ }
+ synchronized(this) {
+ notifyAll();
+ }
+ }
+
+ /**
+ * Retrieves the next task from the load queue, as well as whether we want that task to be
+ * force reloaded.
+ */
+ Task nextTask() {
+ return mQueue.poll();
+ }
+
+ /** Removes a task from the load queue */
+ void removeTask(Task t) {
+ mQueue.remove(t);
+ }
+
+ /** Clears all the tasks from the load queue */
+ void clearTasks() {
+ mQueue.clear();
+ }
+
+ /** Returns whether the load queue is empty */
+ boolean isEmpty() {
+ return mQueue.isEmpty();
+ }
+}
diff --git a/com/android/systemui/shared/recents/model/TaskStack.java b/com/android/systemui/shared/recents/model/TaskStack.java
new file mode 100644
index 00000000..693379d3
--- /dev/null
+++ b/com/android/systemui/shared/recents/model/TaskStack.java
@@ -0,0 +1,403 @@
+/*
+ * 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.systemui.shared.recents.model;
+
+import android.content.ComponentName;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * The task stack contains a list of multiple tasks.
+ */
+public class TaskStack {
+
+ private static final String TAG = "TaskStack";
+
+ /** Task stack callbacks */
+ public interface TaskStackCallbacks {
+ /**
+ * Notifies when a new task has been added to the stack.
+ */
+ void onStackTaskAdded(TaskStack stack, Task newTask);
+
+ /**
+ * Notifies when a task has been removed from the stack.
+ */
+ void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
+ AnimationProps animation, boolean fromDockGesture,
+ boolean dismissRecentsIfAllRemoved);
+
+ /**
+ * Notifies when all tasks have been removed from the stack.
+ */
+ void onStackTasksRemoved(TaskStack stack);
+
+ /**
+ * Notifies when tasks in the stack have been updated.
+ */
+ void onStackTasksUpdated(TaskStack stack);
+ }
+
+ private final ArrayList<Task> mRawTaskList = new ArrayList<>();
+ private final FilteredTaskList mStackTaskList = new FilteredTaskList();
+ private TaskStackCallbacks mCb;
+
+ public TaskStack() {
+ // Ensure that we only show stack tasks
+ mStackTaskList.setFilter((taskIdMap, t, index) -> t.isStackTask);
+ }
+
+ /** Sets the callbacks for this task stack. */
+ public void setCallbacks(TaskStackCallbacks cb) {
+ mCb = cb;
+ }
+
+ /**
+ * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
+ * how they should update themselves.
+ */
+ public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
+ removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */);
+ }
+
+ /**
+ * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
+ * how they should update themselves.
+ */
+ public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture,
+ boolean dismissRecentsIfAllRemoved) {
+ if (mStackTaskList.contains(t)) {
+ mStackTaskList.remove(t);
+ Task newFrontMostTask = getStackFrontMostTask();
+ if (mCb != null) {
+ // Notify that a task has been removed
+ mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
+ fromDockGesture, dismissRecentsIfAllRemoved);
+ }
+ }
+ mRawTaskList.remove(t);
+ }
+
+ /**
+ * Removes all tasks from the stack.
+ */
+ public void removeAllTasks(boolean notifyStackChanges) {
+ ArrayList<Task> tasks = mStackTaskList.getTasks();
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ Task t = tasks.get(i);
+ mStackTaskList.remove(t);
+ mRawTaskList.remove(t);
+ }
+ if (mCb != null && notifyStackChanges) {
+ // Notify that all tasks have been removed
+ mCb.onStackTasksRemoved(this);
+ }
+ }
+
+
+ /**
+ * @see #setTasks(List, boolean)
+ */
+ public void setTasks(TaskStack stack, boolean notifyStackChanges) {
+ setTasks(stack.mRawTaskList, notifyStackChanges);
+ }
+
+ /**
+ * Sets a few tasks in one go, without calling any callbacks.
+ *
+ * @param tasks the new set of tasks to replace the current set.
+ * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
+ */
+ public void setTasks(List<Task> tasks, boolean notifyStackChanges) {
+ // Compute a has set for each of the tasks
+ ArrayMap<TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
+ ArrayMap<TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
+ ArrayList<Task> addedTasks = new ArrayList<>();
+ ArrayList<Task> removedTasks = new ArrayList<>();
+ ArrayList<Task> allTasks = new ArrayList<>();
+
+ // Disable notifications if there are no callbacks
+ if (mCb == null) {
+ notifyStackChanges = false;
+ }
+
+ // Remove any tasks that no longer exist
+ int taskCount = mRawTaskList.size();
+ for (int i = taskCount - 1; i >= 0; i--) {
+ Task task = mRawTaskList.get(i);
+ if (!newTasksMap.containsKey(task.key)) {
+ if (notifyStackChanges) {
+ removedTasks.add(task);
+ }
+ }
+ }
+
+ // Add any new tasks
+ taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task newTask = tasks.get(i);
+ Task currentTask = currentTasksMap.get(newTask.key);
+ if (currentTask == null && notifyStackChanges) {
+ addedTasks.add(newTask);
+ } else if (currentTask != null) {
+ // The current task has bound callbacks, so just copy the data from the new task
+ // state and add it back into the list
+ currentTask.copyFrom(newTask);
+ newTask = currentTask;
+ }
+ allTasks.add(newTask);
+ }
+
+ // Sort all the tasks to ensure they are ordered correctly
+ for (int i = allTasks.size() - 1; i >= 0; i--) {
+ allTasks.get(i).temporarySortIndexInStack = i;
+ }
+
+ mStackTaskList.set(allTasks);
+ mRawTaskList.clear();
+ mRawTaskList.addAll(allTasks);
+
+ // Only callback for the removed tasks after the stack has updated
+ int removedTaskCount = removedTasks.size();
+ Task newFrontMostTask = getStackFrontMostTask();
+ for (int i = 0; i < removedTaskCount; i++) {
+ mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
+ AnimationProps.IMMEDIATE, false /* fromDockGesture */,
+ true /* dismissRecentsIfAllRemoved */);
+ }
+
+ // Only callback for the newly added tasks after this stack has been updated
+ int addedTaskCount = addedTasks.size();
+ for (int i = 0; i < addedTaskCount; i++) {
+ mCb.onStackTaskAdded(this, addedTasks.get(i));
+ }
+
+ // Notify that the task stack has been updated
+ if (notifyStackChanges) {
+ mCb.onStackTasksUpdated(this);
+ }
+ }
+
+ /**
+ * Gets the front-most task in the stack.
+ */
+ public Task getStackFrontMostTask() {
+ ArrayList<Task> stackTasks = mStackTaskList.getTasks();
+ if (stackTasks.isEmpty()) {
+ return null;
+ }
+ return stackTasks.get(stackTasks.size() - 1);
+ }
+
+ /** Gets the task keys */
+ public ArrayList<TaskKey> getTaskKeys() {
+ ArrayList<TaskKey> taskKeys = new ArrayList<>();
+ ArrayList<Task> tasks = computeAllTasksList();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ taskKeys.add(task.key);
+ }
+ return taskKeys;
+ }
+
+ /**
+ * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
+ */
+ public ArrayList<Task> getStackTasks() {
+ return mStackTaskList.getTasks();
+ }
+
+ /**
+ * Computes a set of all the active and historical tasks.
+ */
+ public ArrayList<Task> computeAllTasksList() {
+ ArrayList<Task> tasks = new ArrayList<>();
+ tasks.addAll(mStackTaskList.getTasks());
+ return tasks;
+ }
+
+ /**
+ * Returns the number of stacktasks.
+ */
+ public int getTaskCount() {
+ return mStackTaskList.size();
+ }
+
+ /**
+ * Returns the number of stack tasks.
+ */
+ public int getStackTaskCount() {
+ return mStackTaskList.size();
+ }
+
+ /**
+ * Returns the task in stack tasks which is the launch target.
+ */
+ public Task getLaunchTarget() {
+ ArrayList<Task> tasks = mStackTaskList.getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ if (task.isLaunchTarget) {
+ return task;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether the next launch target should actually be the PiP task.
+ */
+ public boolean isNextLaunchTargetPip(long lastPipTime) {
+ Task launchTarget = getLaunchTarget();
+ Task nextLaunchTarget = getNextLaunchTargetRaw();
+ if (nextLaunchTarget != null && lastPipTime > 0) {
+ // If the PiP time is more recent than the next launch target, then launch the PiP task
+ return lastPipTime > nextLaunchTarget.key.lastActiveTime;
+ } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) {
+ // Otherwise, if there is no next launch target, but there is a PiP, then launch
+ // the PiP task
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the task in stack tasks which should be launched next if Recents are toggled
+ * again, or null if there is no task to be launched. Callers should check
+ * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the
+ * stack.
+ */
+ public Task getNextLaunchTarget() {
+ Task nextLaunchTarget = getNextLaunchTargetRaw();
+ if (nextLaunchTarget != null) {
+ return nextLaunchTarget;
+ }
+ return getStackTasks().get(getTaskCount() - 1);
+ }
+
+ private Task getNextLaunchTargetRaw() {
+ int taskCount = getTaskCount();
+ if (taskCount == 0) {
+ return null;
+ }
+ int launchTaskIndex = indexOfStackTask(getLaunchTarget());
+ if (launchTaskIndex != -1 && launchTaskIndex > 0) {
+ return getStackTasks().get(launchTaskIndex - 1);
+ }
+ return null;
+ }
+
+ /** Returns the index of this task in this current task stack */
+ public int indexOfStackTask(Task t) {
+ return mStackTaskList.indexOf(t);
+ }
+
+ /** Finds the task with the specified task id. */
+ public Task findTaskWithId(int taskId) {
+ ArrayList<Task> tasks = computeAllTasksList();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ if (task.key.id == taskId) {
+ return task;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Computes the components of tasks in this stack that have been removed as a result of a change
+ * in the specified package.
+ */
+ public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
+ // Identify all the tasks that should be removed as a result of the package being removed.
+ // Using a set to ensure that we callback once per unique component.
+ ArraySet<ComponentName> existingComponents = new ArraySet<>();
+ ArraySet<ComponentName> removedComponents = new ArraySet<>();
+ ArrayList<TaskKey> taskKeys = getTaskKeys();
+ int taskKeyCount = taskKeys.size();
+ for (int i = 0; i < taskKeyCount; i++) {
+ TaskKey t = taskKeys.get(i);
+
+ // Skip if this doesn't apply to the current user
+ if (t.userId != userId) continue;
+
+ ComponentName cn = t.getComponent();
+ if (cn.getPackageName().equals(packageName)) {
+ if (existingComponents.contains(cn)) {
+ // If we know that the component still exists in the package, then skip
+ continue;
+ }
+ if (PackageManagerWrapper.getInstance().getActivityInfo(cn, userId) != null) {
+ existingComponents.add(cn);
+ } else {
+ removedComponents.add(cn);
+ }
+ }
+ }
+ return removedComponents;
+ }
+
+ @Override
+ public String toString() {
+ String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
+ ArrayList<Task> tasks = mStackTaskList.getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ str += " " + tasks.get(i).toString() + "\n";
+ }
+ return str;
+ }
+
+ /**
+ * Given a list of tasks, returns a map of each task's key to the task.
+ */
+ private ArrayMap<TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
+ ArrayMap<TaskKey, Task> map = new ArrayMap<>(tasks.size());
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ map.put(task.key, task);
+ }
+ return map;
+ }
+
+ public void dump(String prefix, PrintWriter writer) {
+ String innerPrefix = prefix + " ";
+
+ writer.print(prefix); writer.print(TAG);
+ writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
+ writer.println();
+ ArrayList<Task> tasks = mStackTaskList.getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ tasks.get(i).dump(innerPrefix, writer);
+ }
+ }
+}
diff --git a/com/android/systemui/recents/model/ThumbnailData.java b/com/android/systemui/shared/recents/model/ThumbnailData.java
index 33ff1b63..dd1763bb 100644
--- a/com/android/systemui/recents/model/ThumbnailData.java
+++ b/com/android/systemui/shared/recents/model/ThumbnailData.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.recents.model;
+package com.android.systemui.shared.recents.model;
+
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import android.app.ActivityManager.TaskSnapshot;
import android.graphics.Bitmap;
@@ -25,20 +27,25 @@ import android.graphics.Rect;
*/
public class ThumbnailData {
- // TODO: Make these final once the non-snapshot path is removed.
- public Bitmap thumbnail;
+ public final Bitmap thumbnail;
public int orientation;
- public final Rect insets = new Rect();
+ public Rect insets;
public boolean reducedResolution;
public float scale;
- public static ThumbnailData createFromTaskSnapshot(TaskSnapshot snapshot) {
- ThumbnailData out = new ThumbnailData();
- out.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
- out.insets.set(snapshot.getContentInsets());
- out.orientation = snapshot.getOrientation();
- out.reducedResolution = snapshot.isReducedResolution();
- out.scale = snapshot.getScale();
- return out;
+ public ThumbnailData() {
+ thumbnail = null;
+ orientation = ORIENTATION_UNDEFINED;
+ insets = new Rect();
+ reducedResolution = false;
+ scale = 1f;
+ }
+
+ public ThumbnailData(TaskSnapshot snapshot) {
+ thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
+ insets = new Rect(snapshot.getContentInsets());
+ orientation = snapshot.getOrientation();
+ reducedResolution = snapshot.isReducedResolution();
+ scale = snapshot.getScale();
}
}
diff --git a/com/android/systemui/recents/views/AnimationProps.java b/com/android/systemui/shared/recents/utilities/AnimationProps.java
index 716d1bcf..2de7f74b 100644
--- a/com/android/systemui/recents/views/AnimationProps.java
+++ b/com/android/systemui/shared/recents/utilities/AnimationProps.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.recents.views;
+package com.android.systemui.shared.recents.utilities;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -24,8 +24,7 @@ import android.util.SparseArray;
import android.util.SparseLongArray;
import android.view.View;
import android.view.animation.Interpolator;
-
-import com.android.systemui.Interpolators;
+import android.view.animation.LinearInterpolator;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -37,7 +36,8 @@ import java.util.List;
*/
public class AnimationProps {
- public static final AnimationProps IMMEDIATE = new AnimationProps(0, Interpolators.LINEAR);
+ private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+ public static final AnimationProps IMMEDIATE = new AnimationProps(0, LINEAR_INTERPOLATOR);
@Retention(RetentionPolicy.SOURCE)
@IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS})
@@ -51,7 +51,6 @@ public class AnimationProps {
public static final int SCALE = 5;
public static final int BOUNDS = 6;
public static final int DIM_ALPHA = 7;
- public static final int FOCUS_STATE = 8;
private SparseLongArray mPropStartDelay;
private SparseLongArray mPropDuration;
@@ -195,9 +194,9 @@ public class AnimationProps {
if (interp != null) {
return interp;
}
- return mPropInterpolators.get(ALL, Interpolators.LINEAR);
+ return mPropInterpolators.get(ALL, LINEAR_INTERPOLATOR);
}
- return Interpolators.LINEAR;
+ return LINEAR_INTERPOLATOR;
}
/**
diff --git a/com/android/systemui/recents/misc/RectFEvaluator.java b/com/android/systemui/shared/recents/utilities/RectFEvaluator.java
index 72511de9..51c1b5aa 100644
--- a/com/android/systemui/recents/misc/RectFEvaluator.java
+++ b/com/android/systemui/shared/recents/utilities/RectFEvaluator.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.recents.misc;
+package com.android.systemui.shared.recents.utilities;
import android.animation.TypeEvaluator;
import android.graphics.RectF;
@@ -23,7 +23,7 @@ import android.graphics.RectF;
*/
public class RectFEvaluator implements TypeEvaluator<RectF> {
- private RectF mRect = new RectF();
+ private final RectF mRect = new RectF();
/**
* This function returns the result of linearly interpolating the start and
diff --git a/com/android/systemui/recents/misc/Utilities.java b/com/android/systemui/shared/recents/utilities/Utilities.java
index 4349e30f..a5d19639 100644
--- a/com/android/systemui/recents/misc/Utilities.java
+++ b/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.recents.misc;
+package com.android.systemui.shared.recents.utilities;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -38,12 +38,8 @@ import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewStub;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.TaskViewTransform;
-
import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
/* Common code */
public class Utilities {
@@ -76,7 +72,6 @@ public class Utilities {
public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator();
public static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
- public static final Rect EMPTY_RECT = new Rect();
/**
* @return the first parent walking up the view hierarchy that has the given class type.
@@ -253,24 +248,6 @@ public class Utilities {
}
/**
- * Updates {@param transforms} to be the same size as {@param tasks}.
- */
- public static void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) {
- // We can reuse the task transforms where possible to reduce object allocation
- int taskTransformCount = transforms.size();
- int taskCount = tasks.size();
- if (taskTransformCount < taskCount) {
- // If there are less transforms than tasks, then add as many transforms as necessary
- for (int i = taskTransformCount; i < taskCount; i++) {
- transforms.add(new TaskViewTransform());
- }
- } else if (taskTransformCount > taskCount) {
- // If there are more transforms than tasks, then just subset the transform list
- transforms.subList(taskCount, taskTransformCount).clear();
- }
- }
-
- /**
* Used for debugging, converts DP to PX.
*/
public static float dpToPx(Resources res, float dp) {
diff --git a/com/android/systemui/shared/system/ActivityManagerWrapper.java b/com/android/systemui/shared/system/ActivityManagerWrapper.java
new file mode 100644
index 00000000..3f93f76a
--- /dev/null
+++ b/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -0,0 +1,201 @@
+/*
+ * 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.systemui.shared.system;
+
+import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.AppGlobals;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IconDrawableFactory;
+import android.util.Log;
+
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ActivityManagerWrapper {
+
+ private static final String TAG = "ActivityManagerWrapper";
+
+ private static final ActivityManagerWrapper sInstance = new ActivityManagerWrapper();
+
+ private final PackageManager mPackageManager;
+ private final IconDrawableFactory mDrawableFactory;
+
+ private ActivityManagerWrapper() {
+ final Context context = AppGlobals.getInitialApplication();
+ mPackageManager = context.getPackageManager();
+ mDrawableFactory = IconDrawableFactory.newInstance(context);
+ }
+
+ public static ActivityManagerWrapper getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * @return the current user's id.
+ */
+ public int getCurrentUserId() {
+ UserInfo ui;
+ try {
+ ui = ActivityManager.getService().getCurrentUser();
+ return ui != null ? ui.id : 0;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return a list of the recents tasks.
+ */
+ public List<RecentTaskInfo> getRecentTasks(int numTasks, int userId) {
+ try {
+ return ActivityManager.getService().getRecentTasks(numTasks,
+ RECENT_IGNORE_UNAVAILABLE, userId).getList();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get recent tasks", e);
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * @return the task snapshot for the given {@param taskId}.
+ */
+ public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean reducedResolution) {
+ ActivityManager.TaskSnapshot snapshot = null;
+ try {
+ snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to retrieve task snapshot", e);
+ }
+ if (snapshot != null) {
+ return new ThumbnailData(snapshot);
+ } else {
+ return new ThumbnailData();
+ }
+ }
+
+ /**
+ * @return the task description icon, loading and badging it if it necessary.
+ */
+ public Drawable getBadgedTaskDescriptionIcon(Context context,
+ ActivityManager.TaskDescription taskDescription, int userId, Resources res) {
+ Bitmap tdIcon = taskDescription.getInMemoryIcon();
+ Drawable dIcon = null;
+ if (tdIcon != null) {
+ dIcon = new BitmapDrawable(res, tdIcon);
+ } else if (taskDescription.getIconResource() != 0) {
+ try {
+ dIcon = context.getDrawable(taskDescription.getIconResource());
+ } catch (NotFoundException e) {
+ Log.e(TAG, "Could not find icon drawable from resource", e);
+ }
+ } else {
+ tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon(
+ taskDescription.getIconFilename(), userId);
+ if (tdIcon != null) {
+ dIcon = new BitmapDrawable(res, tdIcon);
+ }
+ }
+ if (dIcon != null) {
+ return getBadgedIcon(dIcon, userId);
+ }
+ return null;
+ }
+
+ /**
+ * @return the given icon for a user, badging if necessary.
+ */
+ private Drawable getBadgedIcon(Drawable icon, int userId) {
+ if (userId != UserHandle.myUserId()) {
+ icon = mPackageManager.getUserBadgedIcon(icon, new UserHandle(userId));
+ }
+ return icon;
+ }
+
+ /**
+ * @return the activity icon for the ActivityInfo for a user, badging if necessary.
+ */
+ public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) {
+ return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId);
+ }
+
+ /**
+ * @return the application icon for the ApplicationInfo for a user, badging if necessary.
+ */
+ public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) {
+ return mDrawableFactory.getBadgedIcon(appInfo, userId);
+ }
+
+ /**
+ * @return the activity label, badging if necessary.
+ */
+ public String getBadgedActivityLabel(ActivityInfo info, int userId) {
+ return getBadgedLabel(info.loadLabel(mPackageManager).toString(), userId);
+ }
+
+ /**
+ * @return the application label, badging if necessary.
+ */
+ public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) {
+ return getBadgedLabel(appInfo.loadLabel(mPackageManager).toString(), userId);
+ }
+
+ /**
+ * @return the content description for a given task, badging it if necessary. The content
+ * description joins the app and activity labels.
+ */
+ public String getBadgedContentDescription(ActivityInfo info, int userId,
+ ActivityManager.TaskDescription td) {
+ String activityLabel;
+ if (td != null && td.getLabel() != null) {
+ activityLabel = td.getLabel();
+ } else {
+ activityLabel = info.loadLabel(mPackageManager).toString();
+ }
+ String applicationLabel = info.applicationInfo.loadLabel(mPackageManager).toString();
+ String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
+ return applicationLabel.equals(activityLabel)
+ ? badgedApplicationLabel
+ : badgedApplicationLabel + " " + activityLabel;
+ }
+
+ /**
+ * @return the given label for a user, badging if necessary.
+ */
+ private String getBadgedLabel(String label, int userId) {
+ if (userId != UserHandle.myUserId()) {
+ label = mPackageManager.getUserBadgedLabel(label, new UserHandle(userId)).toString();
+ }
+ return label;
+ }
+}
diff --git a/com/android/systemui/shared/system/PackageManagerWrapper.java b/com/android/systemui/shared/system/PackageManagerWrapper.java
new file mode 100644
index 00000000..d5e6e6ef
--- /dev/null
+++ b/com/android/systemui/shared/system/PackageManagerWrapper.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.system;
+
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+
+public class PackageManagerWrapper {
+
+ private static final String TAG = "PackageManagerWrapper";
+
+ private static final PackageManagerWrapper sInstance = new PackageManagerWrapper();
+
+ private static final IPackageManager mIPackageManager = AppGlobals.getPackageManager();
+
+ public static PackageManagerWrapper getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * @return the activity info for a given {@param componentName} and {@param userId}.
+ */
+ public ActivityInfo getActivityInfo(ComponentName componentName, int userId) {
+ try {
+ return mIPackageManager.getActivityInfo(componentName, PackageManager.GET_META_DATA,
+ userId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 7699bb90..195f4d3f 100644
--- a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -16,37 +16,28 @@
package com.android.systemui.shortcut;
-import android.accessibilityservice.AccessibilityServiceInfo;
+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.os.UserHandle.USER_CURRENT;
+
import android.app.ActivityManager;
-import android.app.IActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.settingslib.accessibility.AccessibilityUtils;
-import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.stackdivider.DividerView;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import java.util.List;
-import java.util.Set;
/**
* Dispatches shortcut to System UI components
@@ -58,7 +49,6 @@ public class ShortcutKeyDispatcher extends SystemUI
private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this);
private IWindowManager mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
- private IActivityManager mActivityManager = ActivityManager.getService();
protected final long META_MASK = ((long) KeyEvent.META_META_ON) << Integer.SIZE;
protected final long ALT_MASK = ((long) KeyEvent.META_ALT_ON) << Integer.SIZE;
@@ -102,11 +92,10 @@ public class ShortcutKeyDispatcher extends SystemUI
// If there is no window docked, we dock the top-most window.
Recents recents = getComponent(Recents.class);
int dockMode = (shortcutCode == SC_DOCK_LEFT)
- ? ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
- : ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+ ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
+ : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
List<ActivityManager.RecentTaskInfo> taskList =
- SystemServicesProxy.getInstance(mContext).getRecentTasks(1,
- UserHandle.USER_CURRENT, false, new ArraySet<>());
+ ActivityManagerWrapper.getInstance().getRecentTasks(1, USER_CURRENT);
recents.showRecentApps(
false /* triggeredFromAltTab */,
false /* fromHome */);
diff --git a/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index 578a18a0..0997983a 100644
--- a/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -32,7 +32,7 @@ import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
import com.android.systemui.recents.events.component.ShowUserToastEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
import com.android.systemui.stackdivider.events.StartedDragingEvent;
import com.android.systemui.stackdivider.events.StoppedDragingEvent;
@@ -76,7 +76,7 @@ public class ForcedResizableInfoActivityController {
mContext = context;
EventBus.getDefault().register(this);
SystemServicesProxy.getInstance(context).registerTaskStackListener(
- new TaskStackListener() {
+ new TaskStackChangeListener() {
@Override
public void onActivityForcedResizable(String packageName, int taskId,
int reason) {
diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 966e7899..6c5f4b23 100644
--- a/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -36,6 +36,7 @@ import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Property;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.NotificationHeaderView;
@@ -174,6 +175,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private boolean mShowNoBackground;
private ExpandableNotificationRow mNotificationParent;
private OnExpandClickListener mOnExpandClickListener;
+
+ // Listener will be called when receiving a long click event.
+ // Use #setLongPressPosition to optionally assign positional data with the long press.
+ private LongPressListener mLongPressListener;
+
private boolean mGroupExpansionChanging;
/**
@@ -788,6 +794,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mOnExpandClickListener = onExpandClickListener;
}
+ public void setLongPressListener(LongPressListener longPressListener) {
+ mLongPressListener = longPressListener;
+ }
+
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
super.setOnClickListener(l);
@@ -1338,6 +1348,47 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ private void doLongClickCallback() {
+ doLongClickCallback(getWidth() / 2, getHeight() / 2);
+ }
+
+ public void doLongClickCallback(int x, int y) {
+ createMenu();
+ MenuItem menuItem = getProvider().getLongpressMenuItem(mContext);
+ if (mLongPressListener != null && menuItem != null) {
+ mLongPressListener.onLongPress(this, x, y, menuItem);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
+ event.startTracking();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
+ if (!event.isCanceled()) {
+ performClick();
+ }
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
+ doLongClickCallback();
+ return true;
+ }
+ return false;
+ }
+
public void resetTranslation() {
if (mTranslateAnim != null) {
mTranslateAnim.cancel();
@@ -2205,6 +2256,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
if (canViewBeDismissed()) {
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
}
@@ -2244,6 +2296,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
case AccessibilityNodeInfo.ACTION_EXPAND:
mExpandClickListener.onClick(this);
return true;
+ case AccessibilityNodeInfo.ACTION_LONG_CLICK:
+ doLongClickCallback();
+ return true;
}
return false;
}
@@ -2332,4 +2387,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) {
mChildrenContainer = childrenContainer;
}
+
+ /**
+ * Equivalent to View.OnLongClickListener with coordinates
+ */
+ public interface LongPressListener {
+ /**
+ * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
+ * @return whether the longpress was handled
+ */
+ boolean onLongPress(View v, int x, int y, MenuItem item);
+ }
}
diff --git a/com/android/systemui/statusbar/car/CarStatusBar.java b/com/android/systemui/statusbar/car/CarStatusBar.java
index 59d3e0a3..fed2ebe9 100644
--- a/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -36,14 +36,18 @@ import android.view.ViewStub;
import android.view.WindowManager;
import android.widget.LinearLayout;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.Dependency;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.SwipeHelper;
+import com.android.systemui.classifier.FalsingLog;
+import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -51,10 +55,6 @@ import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.classifier.FalsingLog;
-import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.Prefs;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -247,11 +247,12 @@ public class CarStatusBar extends StatusBar implements
}
/**
- * Returns the {@link com.android.systemui.SwipeHelper.LongPressListener} that will be
- * triggered when a notification card is long-pressed.
+ * Returns the
+ * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
+ * be triggered when a notification card is long-pressed.
*/
@Override
- protected SwipeHelper.LongPressListener getNotificationLongClicker() {
+ protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
// For the automative use case, we do not want to the user to be able to interact with
// a notification other than a regular click. As a result, just return null for the
// long click listener.
@@ -304,10 +305,10 @@ public class CarStatusBar extends StatusBar implements
}
/**
- * An implementation of TaskStackListener, that listens for changes in the system task
+ * An implementation of TaskStackChangeListener, that listens for changes in the system task
* stack and notifies the navigation bar.
*/
- private class TaskStackListenerImpl extends TaskStackListener {
+ private class TaskStackListenerImpl extends TaskStackChangeListener {
@Override
public void onTaskStackChanged() {
SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 87f5ca7a..40ddf5b4 100644
--- a/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -124,8 +124,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen
} else {
if (selectedUser != null) {
// Show the selected user's static wallpaper.
- return LoaderResult.success(
- mWallpaperManager.getBitmapAsUser(selectedUser.getIdentifier()));
+ return LoaderResult.success(mWallpaperManager.getBitmapAsUser(
+ selectedUser.getIdentifier(), true /* hardware */));
} else {
// When there is no selected user, show the system wallpaper
diff --git a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index f3ca66ff..c9500363 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -17,12 +17,19 @@
package com.android.systemui.statusbar.phone;
import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.SparseArray;
+import android.view.Display;
+import android.view.IWallpaperVisibilityListener;
+import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.View;
+import android.view.WindowManagerGlobal;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
public final class NavigationBarTransitions extends BarTransitions {
@@ -30,6 +37,7 @@ public final class NavigationBarTransitions extends BarTransitions {
private final NavigationBarView mView;
private final IStatusBarService mBarService;
private final LightBarTransitionsController mLightTransitionsController;
+ private boolean mWallpaperVisible;
private boolean mLightsOut;
private boolean mAutoDim;
@@ -41,6 +49,21 @@ public final class NavigationBarTransitions extends BarTransitions {
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mLightTransitionsController = new LightBarTransitionsController(view.getContext(),
this::applyDarkIntensity);
+
+ IWindowManager windowManagerService = Dependency.get(IWindowManager.class);
+ Handler handler = Handler.getMain();
+ try {
+ mWallpaperVisible = windowManagerService.registerWallpaperVisibilityListener(
+ new IWallpaperVisibilityListener.Stub() {
+ @Override
+ public void onWallpaperVisibilityChanged(boolean newVisibility,
+ int displayId) throws RemoteException {
+ mWallpaperVisible = newVisibility;
+ handler.post(() -> applyLightsOut(true, false));
+ }
+ }, Display.DEFAULT_DISPLAY);
+ } catch (RemoteException e) {
+ }
}
public void init() {
@@ -57,7 +80,7 @@ public final class NavigationBarTransitions extends BarTransitions {
@Override
protected boolean isLightsOut(int mode) {
- return super.isLightsOut(mode) || mAutoDim;
+ return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible);
}
public LightBarTransitionsController getLightTransitionsController() {
@@ -85,7 +108,7 @@ public final class NavigationBarTransitions extends BarTransitions {
// ok, everyone, stop it right there
navButtons.animate().cancel();
- final float navButtonsAlpha = lightsOut ? 0.5f : 1f;
+ final float navButtonsAlpha = lightsOut ? 0.6f : 1f;
if (!animate) {
navButtons.setAlpha(navButtonsAlpha);
diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 7b11ace8..af034406 100644
--- a/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -704,7 +704,7 @@ public class NotificationPanelView extends PanelView implements
mInitialHeightOnTouch = mQsExpansionHeight;
mQsTracking = true;
mIntercepting = false;
- mNotificationStackScroller.removeLongPressCallback();
+ mNotificationStackScroller.cancelLongPress();
}
break;
case MotionEvent.ACTION_POINTER_UP:
@@ -740,7 +740,7 @@ public class NotificationPanelView extends PanelView implements
mInitialTouchY = y;
mInitialTouchX = x;
mIntercepting = false;
- mNotificationStackScroller.removeLongPressCallback();
+ mNotificationStackScroller.cancelLongPress();
return true;
}
break;
diff --git a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 9c837ed8..b876286b 100644
--- a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -66,7 +66,7 @@ import com.android.systemui.UiOffloadThread;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.policy.BluetoothController;
@@ -639,12 +639,17 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
}
private Intent getTaskIntent(int taskId, int userId) {
- List<ActivityManager.RecentTaskInfo> tasks = mContext.getSystemService(ActivityManager.class)
- .getRecentTasksForUser(NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId);
- for (int i = 0; i < tasks.size(); i++) {
- if (tasks.get(i).id == taskId) {
- return tasks.get(i).baseIntent;
+ try {
+ final List<ActivityManager.RecentTaskInfo> tasks =
+ ActivityManager.getService().getRecentTasks(
+ NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId).getList();
+ for (int i = 0; i < tasks.size(); i++) {
+ if (tasks.get(i).id == taskId) {
+ return tasks.get(i).baseIntent;
+ }
}
+ } catch (RemoteException e) {
+ // Fall through
}
return null;
}
@@ -763,7 +768,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
}
- private final TaskStackListener mTaskListener = new TaskStackListener() {
+ private final TaskStackChangeListener mTaskListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
// Listen for changes to stacks and then check which instant apps are foreground.
diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java
index 54be857d..9f039543 100644
--- a/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/com/android/systemui/statusbar/phone/StatusBar.java
@@ -160,7 +160,6 @@ import com.android.systemui.Interpolators;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
-import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.UiOffloadThread;
@@ -4838,7 +4837,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onTouchSlopExceeded() {
- mStackScroller.removeLongPressCallback();
+ mStackScroller.cancelLongPress();
mStackScroller.checkSnoozeLeavebehind();
}
@@ -5470,7 +5469,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onDoubleTap(float screenX, float screenY) {
- if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
+ if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
&& mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2);
float viewX = screenX - mTmpInt2[0];
@@ -5882,8 +5881,7 @@ public class StatusBar extends SystemUI implements DemoMode,
List<ActivityManager.RecentTaskInfo> recentTask = null;
try {
recentTask = ActivityManager.getService().getRecentTasks(1,
- ActivityManager.RECENT_WITH_EXCLUDED
- | ActivityManager.RECENT_INCLUDE_PROFILES,
+ ActivityManager.RECENT_WITH_EXCLUDED,
mCurrentUserId).getList();
} catch (RemoteException e) {
// Abandon hope activity manager not running.
@@ -6296,14 +6294,15 @@ public class StatusBar extends SystemUI implements DemoMode,
true /* removeControls */, x, y, true /* resetMenu */);
}
- protected SwipeHelper.LongPressListener getNotificationLongClicker() {
- return new SwipeHelper.LongPressListener() {
+ protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+ return new ExpandableNotificationRow.LongPressListener() {
@Override
public boolean onLongPress(View v, final int x, final int y,
MenuItem item) {
if (!(v instanceof ExpandableNotificationRow)) {
return false;
}
+
if (v.getWindowToken() == null) {
Log.e(TAG, "Trying to show notification guts, but not attached to window");
return false;
@@ -6318,7 +6317,7 @@ public class StatusBar extends SystemUI implements DemoMode,
closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */,
true /* resetMenu */);
- return false;
+ return true;
}
bindGuts(row, item);
NotificationGuts guts = row.getGuts();
@@ -6598,6 +6597,7 @@ public class StatusBar extends SystemUI implements DemoMode,
row.setRemoteViewClickHandler(mOnClickHandler);
row.setInflationCallback(this);
row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
+ row.setLongPressListener(getNotificationLongClicker());
// Get the app name.
// Note that Notification.Builder#bindHeaderAppName has similar logic
diff --git a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 75532d9e..1e14626d 100644
--- a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -240,7 +240,7 @@ public class NotificationStackScrollLayout extends ViewGroup
* motion.
*/
private int mMaxScrollAfterExpand;
- private SwipeHelper.LongPressListener mLongPressListener;
+ private ExpandableNotificationRow.LongPressListener mLongPressListener;
private NotificationMenuRowPlugin mCurrMenuRow;
private View mTranslatingParentView;
@@ -410,7 +410,6 @@ public class NotificationStackScrollLayout extends ViewGroup
mExpandHelper.setEventSource(this);
mExpandHelper.setScrollAdapter(this);
mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
- mSwipeHelper.setLongPressListener(mLongPressListener);
mStackScrollAlgorithm = createStackScrollAlgorithm(context);
initView(context);
mFalsingManager = FalsingManager.getInstance(context);
@@ -884,8 +883,7 @@ public class NotificationStackScrollLayout extends ViewGroup
return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
}
- public void setLongPressListener(SwipeHelper.LongPressListener listener) {
- mSwipeHelper.setLongPressListener(listener);
+ public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) {
mLongPressListener = listener;
}
@@ -1175,7 +1173,7 @@ public class NotificationStackScrollLayout extends ViewGroup
if (v instanceof ExpandableNotificationRow) {
((ExpandableNotificationRow) v).setUserLocked(userLocked);
}
- removeLongPressCallback();
+ cancelLongPress();
requestDisallowInterceptTouchEvent(true);
}
@@ -2581,7 +2579,7 @@ public class NotificationStackScrollLayout extends ViewGroup
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
if (disallowIntercept) {
- mSwipeHelper.removeLongPressCallback();
+ cancelLongPress();
}
}
@@ -3302,7 +3300,7 @@ public class NotificationStackScrollLayout extends ViewGroup
mIsBeingDragged = isDragged;
if (isDragged) {
requestDisallowInterceptTouchEvent(true);
- removeLongPressCallback();
+ cancelLongPress();
}
}
@@ -3310,7 +3308,7 @@ public class NotificationStackScrollLayout extends ViewGroup
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (!hasWindowFocus) {
- removeLongPressCallback();
+ cancelLongPress();
}
}
@@ -3324,7 +3322,7 @@ public class NotificationStackScrollLayout extends ViewGroup
@Override
public void requestDisallowLongPress() {
- removeLongPressCallback();
+ cancelLongPress();
}
@Override
@@ -3332,8 +3330,8 @@ public class NotificationStackScrollLayout extends ViewGroup
mDisallowDismissInThisMotion = true;
}
- public void removeLongPressCallback() {
- mSwipeHelper.removeLongPressCallback();
+ public void cancelLongPress() {
+ mSwipeHelper.cancelLongPress();
}
@Override
diff --git a/com/android/systemui/util/wakelock/DelayedWakeLock.java b/com/android/systemui/util/wakelock/DelayedWakeLock.java
index b8359097..5ec3dff0 100644
--- a/com/android/systemui/util/wakelock/DelayedWakeLock.java
+++ b/com/android/systemui/util/wakelock/DelayedWakeLock.java
@@ -23,7 +23,7 @@ import android.os.Handler;
*/
public class DelayedWakeLock implements WakeLock {
- private static final long RELEASE_DELAY_MS = 100;
+ private static final long RELEASE_DELAY_MS = 120;
private final Handler mHandler;
private final WakeLock mInner;
diff --git a/foo/bar/ComplexDao.java b/foo/bar/ComplexDao.java
index 89859e50..dbb54fbc 100644
--- a/foo/bar/ComplexDao.java
+++ b/foo/bar/ComplexDao.java
@@ -1,69 +1,433 @@
-/*
- * 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 foo.bar;
-import android.arch.persistence.room.*;
-import java.util.List;
+
+import android.arch.lifecycle.ComputableLiveData;
import android.arch.lifecycle.LiveData;
-@Dao
-abstract class ComplexDao {
- static class FullName {
- public int id;
- public String fullName;
- }
+import android.arch.persistence.room.InvalidationTracker.Observer;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.RoomSQLiteQuery;
+import android.arch.persistence.room.util.StringUtil;
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+import java.lang.Integer;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.StringBuilder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Generated;
- private final ComplexDatabase mDb;
+@Generated("android.arch.persistence.room.RoomProcessor")
+public class ComplexDao_Impl extends ComplexDao {
+ private final RoomDatabase __db;
- public ComplexDao(ComplexDatabase db) {
- mDb = db;
+ public ComplexDao_Impl(ComplexDatabase __db) {
+ super(__db);
+ this.__db = __db;
}
- @Transaction
+ @Override
public boolean transactionMethod(int i, String s, long l) {
- return true;
+ __db.beginTransaction();
+ try {
+ boolean _result = super.transactionMethod(i, s, l);
+ __db.setTransactionSuccessful();
+ return _result;
+ } finally {
+ __db.endTransaction();
+ }
}
- @Query("SELECT name || lastName as fullName, uid as id FROM user where uid = :id")
- abstract public List<FullName> fullNames(int id);
+ @Override
+ public List<ComplexDao.FullName> fullNames(int id) {
+ final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?";
+ final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+ int _argIndex = 1;
+ _statement.bindLong(_argIndex, id);
+ final Cursor _cursor = __db.query(_statement);
+ try {
+ final int _cursorIndexOfFullName = _cursor.getColumnIndexOrThrow("fullName");
+ final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
+ final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>(_cursor.getCount());
+ while(_cursor.moveToNext()) {
+ final ComplexDao.FullName _item;
+ _item = new ComplexDao.FullName();
+ _item.fullName = _cursor.getString(_cursorIndexOfFullName);
+ _item.id = _cursor.getInt(_cursorIndexOfId);
+ _result.add(_item);
+ }
+ return _result;
+ } finally {
+ _cursor.close();
+ _statement.release();
+ }
+ }
- @Query("SELECT * FROM user where uid = :id")
- abstract public User getById(int id);
+ @Override
+ public User getById(int id) {
+ final String _sql = "SELECT * FROM user where uid = ?";
+ final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+ int _argIndex = 1;
+ _statement.bindLong(_argIndex, id);
+ final Cursor _cursor = __db.query(_statement);
+ try {
+ final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+ final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+ final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+ final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+ final User _result;
+ if(_cursor.moveToFirst()) {
+ _result = new User();
+ _result.uid = _cursor.getInt(_cursorIndexOfUid);
+ _result.name = _cursor.getString(_cursorIndexOfName);
+ final String _tmpLastName;
+ _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+ _result.setLastName(_tmpLastName);
+ _result.age = _cursor.getInt(_cursorIndexOfAge);
+ } else {
+ _result = null;
+ }
+ return _result;
+ } finally {
+ _cursor.close();
+ _statement.release();
+ }
+ }
- @Query("SELECT * FROM user where name LIKE :name AND lastName LIKE :lastName")
- abstract public User findByName(String name, String lastName);
+ @Override
+ public User findByName(String name, String lastName) {
+ final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?";
+ final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 2);
+ int _argIndex = 1;
+ if (name == null) {
+ _statement.bindNull(_argIndex);
+ } else {
+ _statement.bindString(_argIndex, name);
+ }
+ _argIndex = 2;
+ if (lastName == null) {
+ _statement.bindNull(_argIndex);
+ } else {
+ _statement.bindString(_argIndex, lastName);
+ }
+ final Cursor _cursor = __db.query(_statement);
+ try {
+ final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+ final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+ final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+ final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+ final User _result;
+ if(_cursor.moveToFirst()) {
+ _result = new User();
+ _result.uid = _cursor.getInt(_cursorIndexOfUid);
+ _result.name = _cursor.getString(_cursorIndexOfName);
+ final String _tmpLastName;
+ _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+ _result.setLastName(_tmpLastName);
+ _result.age = _cursor.getInt(_cursorIndexOfAge);
+ } else {
+ _result = null;
+ }
+ return _result;
+ } finally {
+ _cursor.close();
+ _statement.release();
+ }
+ }
- @Query("SELECT * FROM user where uid IN (:ids)")
- abstract public List<User> loadAllByIds(int... ids);
+ @Override
+ public List<User> loadAllByIds(int... ids) {
+ StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ _stringBuilder.append("SELECT * FROM user where uid IN (");
+ final int _inputSize = ids.length;
+ StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+ _stringBuilder.append(")");
+ final String _sql = _stringBuilder.toString();
+ final int _argCount = 0 + _inputSize;
+ final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+ int _argIndex = 1;
+ for (int _item : ids) {
+ _statement.bindLong(_argIndex, _item);
+ _argIndex ++;
+ }
+ final Cursor _cursor = __db.query(_statement);
+ try {
+ final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+ final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+ final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+ final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+ final List<User> _result = new ArrayList<User>(_cursor.getCount());
+ while(_cursor.moveToNext()) {
+ final User _item_1;
+ _item_1 = new User();
+ _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
+ _item_1.name = _cursor.getString(_cursorIndexOfName);
+ final String _tmpLastName;
+ _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+ _item_1.setLastName(_tmpLastName);
+ _item_1.age = _cursor.getInt(_cursorIndexOfAge);
+ _result.add(_item_1);
+ }
+ return _result;
+ } finally {
+ _cursor.close();
+ _statement.release();
+ }
+ }
- @Query("SELECT ageColumn FROM user where uid = :id")
- abstract int getAge(int id);
+ @Override
+ int getAge(int id) {
+ final String _sql = "SELECT ageColumn FROM user where uid = ?";
+ final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+ int _argIndex = 1;
+ _statement.bindLong(_argIndex, id);
+ final Cursor _cursor = __db.query(_statement);
+ try {
+ final int _result;
+ if(_cursor.moveToFirst()) {
+ _result = _cursor.getInt(0);
+ } else {
+ _result = 0;
+ }
+ return _result;
+ } finally {
+ _cursor.close();
+ _statement.release();
+ }
+ }
- @Query("SELECT ageColumn FROM user where uid IN(:ids)")
- abstract public int[] getAllAges(int... ids);
+ @Override
+ public int[] getAllAges(int... ids) {
+ StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+ final int _inputSize = ids.length;
+ StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+ _stringBuilder.append(")");
+ final String _sql = _stringBuilder.toString();
+ final int _argCount = 0 + _inputSize;
+ final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+ int _argIndex = 1;
+ for (int _item : ids) {
+ _statement.bindLong(_argIndex, _item);
+ _argIndex ++;
+ }
+ final Cursor _cursor = __db.query(_statement);
+ try {
+ final int[] _result = new int[_cursor.getCount()];
+ int _index = 0;
+ while(_cursor.moveToNext()) {
+ final int _item_1;
+ _item_1 = _cursor.getInt(0);
+ _result[_index] = _item_1;
+ _index ++;
+ }
+ return _result;
+ } finally {
+ _cursor.close();
+ _statement.release();
+ }
+ }
+
+ @Override
+ public List<Integer> getAllAgesAsList(List<Integer> ids) {
+ StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+ final int _inputSize = ids.size();
+ StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+ _stringBuilder.append(")");
+ final String _sql = _stringBuilder.toString();
+ final int _argCount = 0 + _inputSize;
+ final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+ int _argIndex = 1;
+ for (Integer _item : ids) {
+ if (_item == null) {
+ _statement.bindNull(_argIndex);
+ } else {
+ _statement.bindLong(_argIndex, _item);
+ }
+ _argIndex ++;
+ }
+ final Cursor _cursor = __db.query(_statement);
+ try {
+ final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
+ while(_cursor.moveToNext()) {
+ final Integer _item_1;
+ if (_cursor.isNull(0)) {
+ _item_1 = null;
+ } else {
+ _item_1 = _cursor.getInt(0);
+ }
+ _result.add(_item_1);
+ }
+ return _result;
+ } finally {
+ _cursor.close();
+ _statement.release();
+ }
+ }
- @Query("SELECT ageColumn FROM user where uid IN(:ids)")
- abstract public List<Integer> getAllAgesAsList(List<Integer> ids);
+ @Override
+ public LiveData<User> getByIdLive(int id) {
+ final String _sql = "SELECT * FROM user where uid = ?";
+ final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
+ int _argIndex = 1;
+ _statement.bindLong(_argIndex, id);
+ return new ComputableLiveData<User>() {
+ private Observer _observer;
- @Query("SELECT * FROM user where uid = :id")
- abstract public LiveData<User> getByIdLive(int id);
+ @Override
+ protected User compute() {
+ if (_observer == null) {
+ _observer = new Observer("user") {
+ @Override
+ public void onInvalidated(@NonNull Set<String> tables) {
+ invalidate();
+ }
+ };
+ __db.getInvalidationTracker().addWeakObserver(_observer);
+ }
+ final Cursor _cursor = __db.query(_statement);
+ try {
+ final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+ final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+ final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+ final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+ final User _result;
+ if(_cursor.moveToFirst()) {
+ _result = new User();
+ _result.uid = _cursor.getInt(_cursorIndexOfUid);
+ _result.name = _cursor.getString(_cursorIndexOfName);
+ final String _tmpLastName;
+ _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+ _result.setLastName(_tmpLastName);
+ _result.age = _cursor.getInt(_cursorIndexOfAge);
+ } else {
+ _result = null;
+ }
+ return _result;
+ } finally {
+ _cursor.close();
+ }
+ }
+
+ @Override
+ protected void finalize() {
+ _statement.release();
+ }
+ }.getLiveData();
+ }
- @Query("SELECT * FROM user where uid IN (:ids)")
- abstract public LiveData<List<User>> loadUsersByIdsLive(int... ids);
+ @Override
+ public LiveData<List<User>> loadUsersByIdsLive(int... ids) {
+ StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ _stringBuilder.append("SELECT * FROM user where uid IN (");
+ final int _inputSize = ids.length;
+ StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+ _stringBuilder.append(")");
+ final String _sql = _stringBuilder.toString();
+ final int _argCount = 0 + _inputSize;
+ final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+ int _argIndex = 1;
+ for (int _item : ids) {
+ _statement.bindLong(_argIndex, _item);
+ _argIndex ++;
+ }
+ return new ComputableLiveData<List<User>>() {
+ private Observer _observer;
- @Query("SELECT ageColumn FROM user where uid IN(:ids1) OR uid IN (:ids2) OR uid IN (:ids3)")
- abstract public List<Integer> getAllAgesAsList(List<Integer> ids1,
- int[] ids2, int... ids3);
+ @Override
+ protected List<User> compute() {
+ if (_observer == null) {
+ _observer = new Observer("user") {
+ @Override
+ public void onInvalidated(@NonNull Set<String> tables) {
+ invalidate();
+ }
+ };
+ __db.getInvalidationTracker().addWeakObserver(_observer);
+ }
+ final Cursor _cursor = __db.query(_statement);
+ try {
+ final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
+ final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
+ final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
+ final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
+ final List<User> _result = new ArrayList<User>(_cursor.getCount());
+ while(_cursor.moveToNext()) {
+ final User _item_1;
+ _item_1 = new User();
+ _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
+ _item_1.name = _cursor.getString(_cursorIndexOfName);
+ final String _tmpLastName;
+ _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
+ _item_1.setLastName(_tmpLastName);
+ _item_1.age = _cursor.getInt(_cursorIndexOfAge);
+ _result.add(_item_1);
+ }
+ return _result;
+ } finally {
+ _cursor.close();
+ }
+ }
+
+ @Override
+ protected void finalize() {
+ _statement.release();
+ }
+ }.getLiveData();
+ }
+
+ @Override
+ public List<Integer> getAllAgesAsList(List<Integer> ids1, int[] ids2, int... ids3) {
+ StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
+ final int _inputSize = ids1.size();
+ StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+ _stringBuilder.append(") OR uid IN (");
+ final int _inputSize_1 = ids2.length;
+ StringUtil.appendPlaceholders(_stringBuilder, _inputSize_1);
+ _stringBuilder.append(") OR uid IN (");
+ final int _inputSize_2 = ids3.length;
+ StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2);
+ _stringBuilder.append(")");
+ final String _sql = _stringBuilder.toString();
+ final int _argCount = 0 + _inputSize + _inputSize_1 + _inputSize_2;
+ final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
+ int _argIndex = 1;
+ for (Integer _item : ids1) {
+ if (_item == null) {
+ _statement.bindNull(_argIndex);
+ } else {
+ _statement.bindLong(_argIndex, _item);
+ }
+ _argIndex ++;
+ }
+ _argIndex = 1 + _inputSize;
+ for (int _item_1 : ids2) {
+ _statement.bindLong(_argIndex, _item_1);
+ _argIndex ++;
+ }
+ _argIndex = 1 + _inputSize + _inputSize_1;
+ for (int _item_2 : ids3) {
+ _statement.bindLong(_argIndex, _item_2);
+ _argIndex ++;
+ }
+ final Cursor _cursor = __db.query(_statement);
+ try {
+ final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
+ while(_cursor.moveToNext()) {
+ final Integer _item_3;
+ if (_cursor.isNull(0)) {
+ _item_3 = null;
+ } else {
+ _item_3 = _cursor.getInt(0);
+ }
+ _result.add(_item_3);
+ }
+ return _result;
+ } finally {
+ _cursor.close();
+ _statement.release();
+ }
+ }
}
diff --git a/foo/bar/ComplexDatabase.java b/foo/bar/ComplexDatabase.java
index f35e0b8a..cfdc1101 100644
--- a/foo/bar/ComplexDatabase.java
+++ b/foo/bar/ComplexDatabase.java
@@ -1,23 +1,99 @@
-/*
- * 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 foo.bar;
-import android.arch.persistence.room.*;
-import java.util.List;
-@Database(entities = {User.class}, version = 1923)
-abstract class ComplexDatabase extends RoomDatabase {
- abstract ComplexDao getComplexDao();
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.SupportSQLiteOpenHelper;
+import android.arch.persistence.db.SupportSQLiteOpenHelper.Callback;
+import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration;
+import android.arch.persistence.room.DatabaseConfiguration;
+import android.arch.persistence.room.InvalidationTracker;
+import android.arch.persistence.room.RoomOpenHelper;
+import android.arch.persistence.room.RoomOpenHelper.Delegate;
+import android.arch.persistence.room.util.TableInfo;
+import android.arch.persistence.room.util.TableInfo.Column;
+import android.arch.persistence.room.util.TableInfo.ForeignKey;
+import android.arch.persistence.room.util.TableInfo.Index;
+import java.lang.IllegalStateException;
+import java.lang.Override;
+import java.lang.String;
+import java.util.HashMap;
+import java.util.HashSet;
+import javax.annotation.Generated;
+
+@Generated("android.arch.persistence.room.RoomProcessor")
+public class ComplexDatabase_Impl extends ComplexDatabase {
+ private volatile ComplexDao _complexDao;
+
+ protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
+ final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1923) {
+ public void createAllTables(SupportSQLiteDatabase _db) {
+ _db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`uid` INTEGER NOT NULL, `name` TEXT, `lastName` TEXT, `ageColumn` INTEGER NOT NULL, PRIMARY KEY(`uid`))");
+ _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
+ _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6773601c5bcf94c71ee4eb0de04f21a4\")");
+ }
+
+ public void dropAllTables(SupportSQLiteDatabase _db) {
+ _db.execSQL("DROP TABLE IF EXISTS `User`");
+ }
+
+ protected void onCreate(SupportSQLiteDatabase _db) {
+ if (mCallbacks != null) {
+ for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
+ mCallbacks.get(_i).onCreate(_db);
+ }
+ }
+ }
+
+ public void onOpen(SupportSQLiteDatabase _db) {
+ mDatabase = _db;
+ internalInitInvalidationTracker(_db);
+ if (mCallbacks != null) {
+ for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
+ mCallbacks.get(_i).onOpen(_db);
+ }
+ }
+ }
+
+ protected void validateMigration(SupportSQLiteDatabase _db) {
+ final HashMap<String, TableInfo.Column> _columnsUser = new HashMap<String, TableInfo.Column>(4);
+ _columnsUser.put("uid", new TableInfo.Column("uid", "INTEGER", true, 1));
+ _columnsUser.put("name", new TableInfo.Column("name", "TEXT", false, 0));
+ _columnsUser.put("lastName", new TableInfo.Column("lastName", "TEXT", false, 0));
+ _columnsUser.put("ageColumn", new TableInfo.Column("ageColumn", "INTEGER", true, 0));
+ final HashSet<TableInfo.ForeignKey> _foreignKeysUser = new HashSet<TableInfo.ForeignKey>(0);
+ final HashSet<TableInfo.Index> _indicesUser = new HashSet<TableInfo.Index>(0);
+ final TableInfo _infoUser = new TableInfo("User", _columnsUser, _foreignKeysUser, _indicesUser);
+ final TableInfo _existingUser = TableInfo.read(_db, "User");
+ if (! _infoUser.equals(_existingUser)) {
+ throw new IllegalStateException("Migration didn't properly handle User(foo.bar.User).\n"
+ + " Expected:\n" + _infoUser + "\n"
+ + " Found:\n" + _existingUser);
+ }
+ }
+ }, "6773601c5bcf94c71ee4eb0de04f21a4");
+ final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
+ .name(configuration.name)
+ .callback(_openCallback)
+ .build();
+ final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
+ return _helper;
+ }
+
+ @Override
+ protected InvalidationTracker createInvalidationTracker() {
+ return new InvalidationTracker(this, "User");
+ }
+
+ @Override
+ ComplexDao getComplexDao() {
+ if (_complexDao != null) {
+ return _complexDao;
+ } else {
+ synchronized(this) {
+ if(_complexDao == null) {
+ _complexDao = new ComplexDao_Impl(this);
+ }
+ return _complexDao;
+ }
+ }
+ }
}
diff --git a/foo/bar/DeletionDao.java b/foo/bar/DeletionDao.java
index 997f2906..067bf670 100644
--- a/foo/bar/DeletionDao.java
+++ b/foo/bar/DeletionDao.java
@@ -1,51 +1,240 @@
-/*
- * 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 foo.bar;
-import android.arch.persistence.room.*;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.arch.persistence.room.EntityDeletionOrUpdateAdapter;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.SharedSQLiteStatement;
+import android.arch.persistence.room.util.StringUtil;
+import java.lang.Override;
+import java.lang.String;
+import java.lang.StringBuilder;
import java.util.List;
+import javax.annotation.Generated;
+
+@Generated("android.arch.persistence.room.RoomProcessor")
+public class DeletionDao_Impl implements DeletionDao {
+ private final RoomDatabase __db;
+
+ private final EntityDeletionOrUpdateAdapter __deletionAdapterOfUser;
+
+ private final EntityDeletionOrUpdateAdapter __deletionAdapterOfMultiPKeyEntity;
+
+ private final EntityDeletionOrUpdateAdapter __deletionAdapterOfBook;
+
+ private final SharedSQLiteStatement __preparedStmtOfDeleteByUid;
+
+ private final SharedSQLiteStatement __preparedStmtOfDeleteEverything;
+
+ public DeletionDao_Impl(RoomDatabase __db) {
+ this.__db = __db;
+ this.__deletionAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+ @Override
+ public String createQuery() {
+ return "DELETE FROM `User` WHERE `uid` = ?";
+ }
+
+ @Override
+ public void bind(SupportSQLiteStatement stmt, User value) {
+ stmt.bindLong(1, value.uid);
+ }
+ };
+ this.__deletionAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
+ @Override
+ public String createQuery() {
+ return "DELETE FROM `MultiPKeyEntity` WHERE `name` = ? AND `lastName` = ?";
+ }
+
+ @Override
+ public void bind(SupportSQLiteStatement stmt, MultiPKeyEntity value) {
+ if (value.name == null) {
+ stmt.bindNull(1);
+ } else {
+ stmt.bindString(1, value.name);
+ }
+ if (value.lastName == null) {
+ stmt.bindNull(2);
+ } else {
+ stmt.bindString(2, value.lastName);
+ }
+ }
+ };
+ this.__deletionAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
+ @Override
+ public String createQuery() {
+ return "DELETE FROM `Book` WHERE `bookId` = ?";
+ }
+
+ @Override
+ public void bind(SupportSQLiteStatement stmt, Book value) {
+ stmt.bindLong(1, value.bookId);
+ }
+ };
+ this.__preparedStmtOfDeleteByUid = new SharedSQLiteStatement(__db) {
+ @Override
+ public String createQuery() {
+ final String _query = "DELETE FROM user where uid = ?";
+ return _query;
+ }
+ };
+ this.__preparedStmtOfDeleteEverything = new SharedSQLiteStatement(__db) {
+ @Override
+ public String createQuery() {
+ final String _query = "DELETE FROM user";
+ return _query;
+ }
+ };
+ }
+
+ @Override
+ public void deleteUser(User user) {
+ __db.beginTransaction();
+ try {
+ __deletionAdapterOfUser.handle(user);
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public void deleteUsers(User user1, List<User> others) {
+ __db.beginTransaction();
+ try {
+ __deletionAdapterOfUser.handle(user1);
+ __deletionAdapterOfUser.handleMultiple(others);
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public void deleteArrayOfUsers(User[] users) {
+ __db.beginTransaction();
+ try {
+ __deletionAdapterOfUser.handleMultiple(users);
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public int deleteUserAndReturnCount(User user) {
+ int _total = 0;
+ __db.beginTransaction();
+ try {
+ _total +=__deletionAdapterOfUser.handle(user);
+ __db.setTransactionSuccessful();
+ return _total;
+ } finally {
+ __db.endTransaction();
+ }
+ }
-@Dao
-abstract interface DeletionDao {
- @Delete
- void deleteUser(User user);
- @Delete
- void deleteUsers(User user1, List<User> others);
- @Delete
- void deleteArrayOfUsers(User[] users);
+ @Override
+ public int deleteUserAndReturnCount(User user1, List<User> others) {
+ int _total = 0;
+ __db.beginTransaction();
+ try {
+ _total +=__deletionAdapterOfUser.handle(user1);
+ _total +=__deletionAdapterOfUser.handleMultiple(others);
+ __db.setTransactionSuccessful();
+ return _total;
+ } finally {
+ __db.endTransaction();
+ }
+ }
- @Delete
- int deleteUserAndReturnCount(User user);
- @Delete
- int deleteUserAndReturnCount(User user1, List<User> others);
- @Delete
- int deleteUserAndReturnCount(User[] users);
+ @Override
+ public int deleteUserAndReturnCount(User[] users) {
+ int _total = 0;
+ __db.beginTransaction();
+ try {
+ _total +=__deletionAdapterOfUser.handleMultiple(users);
+ __db.setTransactionSuccessful();
+ return _total;
+ } finally {
+ __db.endTransaction();
+ }
+ }
- @Delete
- int multiPKey(MultiPKeyEntity entity);
+ @Override
+ public int multiPKey(MultiPKeyEntity entity) {
+ int _total = 0;
+ __db.beginTransaction();
+ try {
+ _total +=__deletionAdapterOfMultiPKeyEntity.handle(entity);
+ __db.setTransactionSuccessful();
+ return _total;
+ } finally {
+ __db.endTransaction();
+ }
+ }
- @Query("DELETE FROM user where uid = :uid")
- int deleteByUid(int uid);
+ @Override
+ public void deleteUserAndBook(User user, Book book) {
+ __db.beginTransaction();
+ try {
+ __deletionAdapterOfUser.handle(user);
+ __deletionAdapterOfBook.handle(book);
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ }
+ }
- @Query("DELETE FROM user where uid IN(:uid)")
- int deleteByUidList(int... uid);
+ @Override
+ public int deleteByUid(int uid) {
+ final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
+ __db.beginTransaction();
+ try {
+ int _argIndex = 1;
+ _stmt.bindLong(_argIndex, uid);
+ final int _result = _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return _result;
+ } finally {
+ __db.endTransaction();
+ __preparedStmtOfDeleteByUid.release(_stmt);
+ }
+ }
- @Delete
- void deleteUserAndBook(User user, Book book);
+ @Override
+ public int deleteEverything() {
+ final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteEverything.acquire();
+ __db.beginTransaction();
+ try {
+ final int _result = _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return _result;
+ } finally {
+ __db.endTransaction();
+ __preparedStmtOfDeleteEverything.release(_stmt);
+ }
+ }
- @Query("DELETE FROM user")
- int deleteEverything();
+ @Override
+ public int deleteByUidList(int... uid) {
+ StringBuilder _stringBuilder = StringUtil.newStringBuilder();
+ _stringBuilder.append("DELETE FROM user where uid IN(");
+ final int _inputSize = uid.length;
+ StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
+ _stringBuilder.append(")");
+ final String _sql = _stringBuilder.toString();
+ SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
+ int _argIndex = 1;
+ for (int _item : uid) {
+ _stmt.bindLong(_argIndex, _item);
+ _argIndex ++;
+ }
+ __db.beginTransaction();
+ try {
+ final int _result = _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ return _result;
+ } finally {
+ __db.endTransaction();
+ }
+ }
}
diff --git a/foo/bar/UpdateDao.java b/foo/bar/UpdateDao.java
index 040b5c79..1190a0df 100644
--- a/foo/bar/UpdateDao.java
+++ b/foo/bar/UpdateDao.java
@@ -1,48 +1,240 @@
-/*
- * 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 foo.bar;
-import android.arch.persistence.room.*;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.arch.persistence.room.EntityDeletionOrUpdateAdapter;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.SharedSQLiteStatement;
+import java.lang.Override;
+import java.lang.String;
import java.util.List;
+import javax.annotation.Generated;
+
+@Generated("android.arch.persistence.room.RoomProcessor")
+public class UpdateDao_Impl implements UpdateDao {
+ private final RoomDatabase __db;
+
+ private final EntityDeletionOrUpdateAdapter __updateAdapterOfUser;
+
+ private final EntityDeletionOrUpdateAdapter __updateAdapterOfMultiPKeyEntity;
+
+ private final EntityDeletionOrUpdateAdapter __updateAdapterOfBook;
+
+ private final SharedSQLiteStatement __preparedStmtOfAgeUserByUid;
+
+ private final SharedSQLiteStatement __preparedStmtOfAgeUserAll;
+
+ public UpdateDao_Impl(RoomDatabase __db) {
+ this.__db = __db;
+ this.__updateAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
+ @Override
+ public String createQuery() {
+ return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
+ }
+
+ @Override
+ public void bind(SupportSQLiteStatement stmt, User value) {
+ stmt.bindLong(1, value.uid);
+ if (value.name == null) {
+ stmt.bindNull(2);
+ } else {
+ stmt.bindString(2, value.name);
+ }
+ if (value.getLastName() == null) {
+ stmt.bindNull(3);
+ } else {
+ stmt.bindString(3, value.getLastName());
+ }
+ stmt.bindLong(4, value.age);
+ stmt.bindLong(5, value.uid);
+ }
+ };
+ this.__updateAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
+ @Override
+ public String createQuery() {
+ return "UPDATE OR ABORT `MultiPKeyEntity` SET `name` = ?,`lastName` = ? WHERE `name` = ? AND `lastName` = ?";
+ }
+
+ @Override
+ public void bind(SupportSQLiteStatement stmt, MultiPKeyEntity value) {
+ if (value.name == null) {
+ stmt.bindNull(1);
+ } else {
+ stmt.bindString(1, value.name);
+ }
+ if (value.lastName == null) {
+ stmt.bindNull(2);
+ } else {
+ stmt.bindString(2, value.lastName);
+ }
+ if (value.name == null) {
+ stmt.bindNull(3);
+ } else {
+ stmt.bindString(3, value.name);
+ }
+ if (value.lastName == null) {
+ stmt.bindNull(4);
+ } else {
+ stmt.bindString(4, value.lastName);
+ }
+ }
+ };
+ this.__updateAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
+ @Override
+ public String createQuery() {
+ return "UPDATE OR ABORT `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
+ }
+
+ @Override
+ public void bind(SupportSQLiteStatement stmt, Book value) {
+ stmt.bindLong(1, value.bookId);
+ stmt.bindLong(2, value.uid);
+ stmt.bindLong(3, value.bookId);
+ }
+ };
+ this.__preparedStmtOfAgeUserByUid = new SharedSQLiteStatement(__db) {
+ @Override
+ public String createQuery() {
+ final String _query = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?";
+ return _query;
+ }
+ };
+ this.__preparedStmtOfAgeUserAll = new SharedSQLiteStatement(__db) {
+ @Override
+ public String createQuery() {
+ final String _query = "UPDATE User SET ageColumn = ageColumn + 1";
+ return _query;
+ }
+ };
+ }
+
+ @Override
+ public void updateUser(User user) {
+ __db.beginTransaction();
+ try {
+ __updateAdapterOfUser.handle(user);
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public void updateUsers(User user1, List<User> others) {
+ __db.beginTransaction();
+ try {
+ __updateAdapterOfUser.handle(user1);
+ __updateAdapterOfUser.handleMultiple(others);
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public void updateArrayOfUsers(User[] users) {
+ __db.beginTransaction();
+ try {
+ __updateAdapterOfUser.handleMultiple(users);
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public int updateUserAndReturnCount(User user) {
+ int _total = 0;
+ __db.beginTransaction();
+ try {
+ _total +=__updateAdapterOfUser.handle(user);
+ __db.setTransactionSuccessful();
+ return _total;
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public int updateUserAndReturnCount(User user1, List<User> others) {
+ int _total = 0;
+ __db.beginTransaction();
+ try {
+ _total +=__updateAdapterOfUser.handle(user1);
+ _total +=__updateAdapterOfUser.handleMultiple(others);
+ __db.setTransactionSuccessful();
+ return _total;
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public int updateUserAndReturnCount(User[] users) {
+ int _total = 0;
+ __db.beginTransaction();
+ try {
+ _total +=__updateAdapterOfUser.handleMultiple(users);
+ __db.setTransactionSuccessful();
+ return _total;
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public int multiPKey(MultiPKeyEntity entity) {
+ int _total = 0;
+ __db.beginTransaction();
+ try {
+ _total +=__updateAdapterOfMultiPKeyEntity.handle(entity);
+ __db.setTransactionSuccessful();
+ return _total;
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public void updateUserAndBook(User user, Book book) {
+ __db.beginTransaction();
+ try {
+ __updateAdapterOfUser.handle(user);
+ __updateAdapterOfBook.handle(book);
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public void ageUserByUid(String uid) {
+ final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserByUid.acquire();
+ __db.beginTransaction();
+ try {
+ int _argIndex = 1;
+ if (uid == null) {
+ _stmt.bindNull(_argIndex);
+ } else {
+ _stmt.bindString(_argIndex, uid);
+ }
+ _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ __preparedStmtOfAgeUserByUid.release(_stmt);
+ }
+ }
-@Dao
-abstract interface UpdateDao {
- @Update
- void updateUser(User user);
- @Update
- void updateUsers(User user1, List<User> others);
- @Update
- void updateArrayOfUsers(User[] users);
-
- @Update
- int updateUserAndReturnCount(User user);
- @Update
- int updateUserAndReturnCount(User user1, List<User> others);
- @Update
- int updateUserAndReturnCount(User[] users);
-
- @Update
- int multiPKey(MultiPKeyEntity entity);
-
- @Update
- void updateUserAndBook(User user, Book book);
-
- @Query("UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = :uid")
- void ageUserByUid(String uid);
-
- @Query("UPDATE User SET ageColumn = ageColumn + 1")
- void ageUserAll();
+ @Override
+ public void ageUserAll() {
+ final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire();
+ __db.beginTransaction();
+ try {
+ _stmt.executeUpdateDelete();
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ __preparedStmtOfAgeUserAll.release(_stmt);
+ }
+ }
}
diff --git a/foo/bar/WriterDao.java b/foo/bar/WriterDao.java
index e122479b..cfad0469 100644
--- a/foo/bar/WriterDao.java
+++ b/foo/bar/WriterDao.java
@@ -15,17 +15,131 @@
*/
package foo.bar;
-import android.arch.persistence.room.*;
+
+import android.arch.persistence.db.SupportSQLiteStatement;
+import android.arch.persistence.room.EntityInsertionAdapter;
+import android.arch.persistence.room.RoomDatabase;
+
+import java.lang.Override;
+import java.lang.String;
import java.util.List;
+import javax.annotation.Generated;
+
+@Generated("android.arch.persistence.room.RoomProcessor")
+public class WriterDao_Impl implements WriterDao {
+ private final RoomDatabase __db;
+
+ private final EntityInsertionAdapter __insertionAdapterOfUser;
+
+ private final EntityInsertionAdapter __insertionAdapterOfUser_1;
+
+ private final EntityInsertionAdapter __insertionAdapterOfBook;
+
+ public WriterDao_Impl(RoomDatabase __db) {
+ this.__db = __db;
+ this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
+ @Override
+ public String createQuery() {
+ return "INSERT OR ABORT INTO `User`(`uid`,`name`,`lastName`,`ageColumn`) VALUES"
+ + " (?,?,?,?)";
+ }
+
+ @Override
+ public void bind(SupportSQLiteStatement stmt, User value) {
+ stmt.bindLong(1, value.uid);
+ if (value.name == null) {
+ stmt.bindNull(2);
+ } else {
+ stmt.bindString(2, value.name);
+ }
+ if (value.getLastName() == null) {
+ stmt.bindNull(3);
+ } else {
+ stmt.bindString(3, value.getLastName());
+ }
+ stmt.bindLong(4, value.age);
+ }
+ };
+ this.__insertionAdapterOfUser_1 = new EntityInsertionAdapter<User>(__db) {
+ @Override
+ public String createQuery() {
+ return "INSERT OR REPLACE INTO `User`(`uid`,`name`,`lastName`,`ageColumn`) VALUES"
+ + " (?,?,?,?)";
+ }
+
+ @Override
+ public void bind(SupportSQLiteStatement stmt, User value) {
+ stmt.bindLong(1, value.uid);
+ if (value.name == null) {
+ stmt.bindNull(2);
+ } else {
+ stmt.bindString(2, value.name);
+ }
+ if (value.getLastName() == null) {
+ stmt.bindNull(3);
+ } else {
+ stmt.bindString(3, value.getLastName());
+ }
+ stmt.bindLong(4, value.age);
+ }
+ };
+ this.__insertionAdapterOfBook = new EntityInsertionAdapter<Book>(__db) {
+ @Override
+ public String createQuery() {
+ return "INSERT OR ABORT INTO `Book`(`bookId`,`uid`) VALUES (?,?)";
+ }
+
+ @Override
+ public void bind(SupportSQLiteStatement stmt, Book value) {
+ stmt.bindLong(1, value.bookId);
+ stmt.bindLong(2, value.uid);
+ }
+ };
+ }
+
+ @Override
+ public void insertUser(User user) {
+ __db.beginTransaction();
+ try {
+ __insertionAdapterOfUser.insert(user);
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public void insertUsers(User user1, List<User> others) {
+ __db.beginTransaction();
+ try {
+ __insertionAdapterOfUser.insert(user1);
+ __insertionAdapterOfUser.insert(others);
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ }
+ }
+
+ @Override
+ public void insertUsers(User[] users) {
+ __db.beginTransaction();
+ try {
+ __insertionAdapterOfUser_1.insert(users);
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ }
+ }
-@Dao
-abstract interface WriterDao {
- @Insert
- void insertUser(User user);
- @Insert
- void insertUsers(User user1, List<User> others);
- @Insert(onConflict=OnConflictStrategy.REPLACE)
- void insertUsers(User[] users);
- @Insert
- void insertUserAndBook(User user, Book book);
+ @Override
+ public void insertUserAndBook(User user, Book book) {
+ __db.beginTransaction();
+ try {
+ __insertionAdapterOfUser.insert(user);
+ __insertionAdapterOfBook.insert(book);
+ __db.setTransactionSuccessful();
+ } finally {
+ __db.endTransaction();
+ }
+ }
}
diff --git a/java/lang/Boolean.java b/java/lang/Boolean.java
index 802d9104..397bf092 100644
--- a/java/lang/Boolean.java
+++ b/java/lang/Boolean.java
@@ -61,6 +61,8 @@ public final class Boolean implements java.io.Serializable,
* @since JDK1.1
*/
@SuppressWarnings("unchecked")
+ // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+ // public static final Class<Boolean> TYPE = (Class<Boolean>) Class.getPrimitiveClass("boolean");
public static final Class<Boolean> TYPE = (Class<Boolean>) boolean[].class.getComponentType();
/**
diff --git a/java/lang/Byte.java b/java/lang/Byte.java
index d0031d0c..2333afd3 100644
--- a/java/lang/Byte.java
+++ b/java/lang/Byte.java
@@ -60,6 +60,8 @@ public final class Byte extends Number implements Comparable<Byte> {
* {@code byte}.
*/
@SuppressWarnings("unchecked")
+ // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+ // public static final Class<Byte> TYPE = (Class<Byte>) Class.getPrimitiveClass("byte");
public static final Class<Byte> TYPE = (Class<Byte>) byte[].class.getComponentType();
/**
diff --git a/java/lang/Character.java b/java/lang/Character.java
index fb0d576e..7615dab7 100644
--- a/java/lang/Character.java
+++ b/java/lang/Character.java
@@ -173,6 +173,8 @@ class Character implements java.io.Serializable, Comparable<Character> {
* @since 1.1
*/
@SuppressWarnings("unchecked")
+ // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+ // public static final Class<Character> TYPE = (Class<Character>) Class.getPrimitiveClass("char");
public static final Class<Character> TYPE = (Class<Character>) char[].class.getComponentType();
/*
diff --git a/java/lang/Double.java b/java/lang/Double.java
index 2bc2bf39..2721a842 100644
--- a/java/lang/Double.java
+++ b/java/lang/Double.java
@@ -137,6 +137,8 @@ public final class Double extends Number implements Comparable<Double> {
* @since JDK1.1
*/
@SuppressWarnings("unchecked")
+ // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+ // public static final Class<Double> TYPE = (Class<Double>) Class.getPrimitiveClass("double");
public static final Class<Double> TYPE = (Class<Double>) double[].class.getComponentType();
/**
diff --git a/java/lang/Float.java b/java/lang/Float.java
index 32bd625a..6a2b9336 100644
--- a/java/lang/Float.java
+++ b/java/lang/Float.java
@@ -135,6 +135,8 @@ public final class Float extends Number implements Comparable<Float> {
* @since JDK1.1
*/
@SuppressWarnings("unchecked")
+ // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+ // public static final Class<Float> TYPE = (Class<Float>) Class.getPrimitiveClass("float");
public static final Class<Float> TYPE = (Class<Float>) float[].class.getComponentType();
/**
diff --git a/java/lang/Integer.java b/java/lang/Integer.java
index f742505a..c63b0d55 100644
--- a/java/lang/Integer.java
+++ b/java/lang/Integer.java
@@ -70,6 +70,8 @@ public final class Integer extends Number implements Comparable<Integer> {
* @since JDK1.1
*/
@SuppressWarnings("unchecked")
+ // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+ // public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
public static final Class<Integer> TYPE = (Class<Integer>) int[].class.getComponentType();
/**
@@ -315,7 +317,8 @@ public final class Integer extends Number implements Comparable<Integer> {
formatUnsignedInt(val, shift, buf, 0, chars);
- // Use special constructor which takes over "buf".
+ // Android-changed: Use regular constructor instead of one which takes over "buf".
+ // return new String(buf, true);
return new String(buf);
}
@@ -340,8 +343,10 @@ public final class Integer extends Number implements Comparable<Integer> {
return charPos;
}
+ // BEGIN Android-changed: Cache the toString() result for small values.
private static final String[] SMALL_NEG_VALUES = new String[100];
private static final String[] SMALL_NONNEG_VALUES = new String[100];
+ // END Android-changed: Cache the toString() result for small values.
final static char [] DigitTens = {
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
@@ -402,7 +407,8 @@ public final class Integer extends Number implements Comparable<Integer> {
if (i == Integer.MIN_VALUE)
return "-2147483648";
- // Android-changed: cache the string literal for small values.
+ // BEGIN Android-changed: Cache the String for small values.
+ // int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
boolean negative = i < 0;
boolean small = negative ? i > -100 : i < 100;
if (small) {
@@ -424,10 +430,12 @@ public final class Integer extends Number implements Comparable<Integer> {
}
return smallValues[i];
}
-
int size = negative ? stringSize(-i) + 1 : stringSize(i);
+ // END Android-changed: Cache the String for small values.
char[] buf = new char[size];
getChars(i, size, buf);
+ // Android-changed: Use regular constructor instead of one which takes over "buf".
+ // return new String(buf, true);
return new String(buf);
}
@@ -567,6 +575,7 @@ public final class Integer extends Number implements Comparable<Integer> {
*/
if (s == null) {
+ // Android-changed: Improve exception message for parseInt.
throw new NumberFormatException("s == null");
}
diff --git a/java/lang/Long.java b/java/lang/Long.java
index 3f383b40..c752957a 100644
--- a/java/lang/Long.java
+++ b/java/lang/Long.java
@@ -72,6 +72,8 @@ public final class Long extends Number implements Comparable<Long> {
* @since JDK1.1
*/
@SuppressWarnings("unchecked")
+ // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+ // public static final Class<Long> TYPE = (Class<Long>) Class.getPrimitiveClass("long");
public static final Class<Long> TYPE = (Class<Long>) long[].class.getComponentType();
/**
@@ -357,6 +359,8 @@ public final class Long extends Number implements Comparable<Long> {
char[] buf = new char[chars];
formatUnsignedLong(val, shift, buf, 0, chars);
+ // Android-changed: Use regular constructor instead of one which takes over "buf".
+ // return new String(buf, true);
return new String(buf);
}
@@ -397,6 +401,8 @@ public final class Long extends Number implements Comparable<Long> {
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
+ // Android-changed: Use regular constructor instead of one which takes over "buf".
+ // return new String(buf, true);
return new String(buf);
}
diff --git a/java/lang/Short.java b/java/lang/Short.java
index 00317114..0f600a84 100644
--- a/java/lang/Short.java
+++ b/java/lang/Short.java
@@ -60,6 +60,8 @@ public final class Short extends Number implements Comparable<Short> {
* {@code short}.
*/
@SuppressWarnings("unchecked")
+ // Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+ // public static final Class<Short> TYPE = (Class<Short>) Class.getPrimitiveClass("short");
public static final Class<Short> TYPE = (Class<Short>) short[].class.getComponentType();
/**
diff --git a/java/lang/String.java b/java/lang/String.java
index 4cefed81..7e823479 100644
--- a/java/lang/String.java
+++ b/java/lang/String.java
@@ -543,8 +543,10 @@ public final class String
throw new UnsupportedOperationException("Use StringFactory instead.");
}
+ // Android-removed: Unused package-private constructor String(char[] value, boolean share).
- // BEGIN Android-changed: Deprecated & unsupported as all calls are intercepted by the runtime.
+ // BEGIN Android-added: Constructor for internal use.
+ // Not implemented in java as all calls are intercepted by the runtime.
/**
* Package private constructor
*
@@ -554,7 +556,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.
+ // END Android-added: Constructor for internal use.
/**
* Returns the length of this string.
@@ -1559,12 +1561,6 @@ 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.
*/
diff --git a/java/lang/Void.java b/java/lang/Void.java
index 8311ea8c..14268c78 100644
--- a/java/lang/Void.java
+++ b/java/lang/Void.java
@@ -44,24 +44,13 @@ class Void {
* The {@code Class} object representing the pseudo-type corresponding to
* the keyword {@code void}.
*/
+ // BEGIN Android-changed: Avoid use of removed Class.getPrimitiveClass method.
+ // public static final Class<Void> TYPE = (Class<Void>) Class.getPrimitiveClass("void");
public static final Class<Void> TYPE = lookupType();
- // Android-changed: Upstream code would use reflection to establish the value of "void.class".
- // ART makes a native call instead because the reflection approach could lead to initialization
- // of TYPE with the current, i.e. uninitialized, value of TYPE due to other Android changes.
@dalvik.annotation.optimization.FastNative
private static native Class<Void> lookupType();
- /*
- @SuppressWarnings("unchecked")
- private static Class<Void> lookupType() {
- try {
- Method method = Runnable.class.getMethod("run", EmptyArray.CLASS);
- return (Class<Void>) method.getReturnType();
- } catch (Exception e) {
- throw new AssertionError(e);
- }
- }
- */
+ // END Android-changed: Avoid use of removed Class.getPrimitiveClass method.
/*
* The Void class cannot be instantiated.
diff --git a/com/android/systemui/recents/misc/NamedCounter.java b/java/lang/invoke/ArrayElementVarHandle.java
index ec3c39cc..e315ba77 100644
--- a/com/android/systemui/recents/misc/NamedCounter.java
+++ b/java/lang/invoke/ArrayElementVarHandle.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 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.
@@ -14,26 +14,22 @@
* limitations under the License.
*/
-package com.android.systemui.recents.misc;
+package java.lang.invoke;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
/**
- * Used to generate successive incremented names.
+ * A VarHandle to access array elements.
+ * @hide
*/
-public class NamedCounter {
-
- int mCount;
- String mPrefix = "";
- String mSuffix = "";
-
- public NamedCounter(String prefix, String suffix) {
- mPrefix = prefix;
- mSuffix = suffix;
+final class ArrayElementVarHandle extends VarHandle {
+ private ArrayElementVarHandle(Class<?> arrayClass) {
+ super(arrayClass.getComponentType(), arrayClass, false /* isFinal */,
+ arrayClass, int.class);
}
- /** Returns the next name. */
- public String nextName() {
- String name = mPrefix + mCount + mSuffix;
- mCount++;
- return name;
+ static ArrayElementVarHandle create(Class<?> arrayClass) {
+ return new ArrayElementVarHandle(arrayClass);
}
}
diff --git a/java/lang/invoke/ByteArrayVarHandle.java b/java/lang/invoke/ByteArrayVarHandle.java
new file mode 100644
index 00000000..4237058d
--- /dev/null
+++ b/java/lang/invoke/ByteArrayVarHandle.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 java.lang.invoke;
+
+import java.nio.ByteOrder;
+
+/**
+ * A VarHandle to access byte array elements as an array of primitive types.
+ * @hide
+ */
+final class ByteArrayVarHandle extends VarHandle {
+ private ByteOrder byteOrder;
+
+ private ByteArrayVarHandle(Class<?> arrayClass, ByteOrder byteOrder) {
+ super(arrayClass.getComponentType(), byte[].class, false /* isFinal */,
+ byte[].class, int.class);
+ this.byteOrder = byteOrder;
+ }
+
+ static ByteArrayVarHandle create(Class<?> arrayClass, ByteOrder byteOrder) {
+ return new ByteArrayVarHandle(arrayClass, byteOrder);
+ }
+}
diff --git a/java/lang/invoke/ByteBufferViewVarHandle.java b/java/lang/invoke/ByteBufferViewVarHandle.java
new file mode 100644
index 00000000..74f3b0cb
--- /dev/null
+++ b/java/lang/invoke/ByteBufferViewVarHandle.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 java.lang.invoke;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * A VarHandle to access byte array elements as an array of primitive types.
+ * @hide
+ */
+final class ByteBufferViewVarHandle extends VarHandle {
+ private ByteOrder byteOrder;
+
+ private ByteBufferViewVarHandle(Class<?> arrayClass, ByteOrder byteOrder) {
+ super(arrayClass.getComponentType(), byte[].class, false /* isFinal */,
+ ByteBuffer.class, int.class);
+ this.byteOrder = byteOrder;
+ }
+
+ static ByteBufferViewVarHandle create(Class<?> arrayClass, ByteOrder byteOrder) {
+ return new ByteBufferViewVarHandle(arrayClass, byteOrder);
+ }
+}
diff --git a/java/lang/invoke/CallSite.java b/java/lang/invoke/CallSite.java
index 85b4bb9f..1ff1eb84 100644
--- a/java/lang/invoke/CallSite.java
+++ b/java/lang/invoke/CallSite.java
@@ -25,363 +25,15 @@
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();
- }
- /**
- * 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;
+ public MethodType type() { return null; }
- // 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/FieldVarHandle.java b/java/lang/invoke/FieldVarHandle.java
new file mode 100644
index 00000000..0921040d
--- /dev/null
+++ b/java/lang/invoke/FieldVarHandle.java
@@ -0,0 +1,46 @@
+/*
+ * 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 java.lang.invoke;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+/**
+ * A VarHandle that's associated with an ArtField.
+ * @hide
+ */
+final class FieldVarHandle extends VarHandle {
+ private final long artField;
+
+ private FieldVarHandle(Field field, Class<?> declaringClass) {
+ super(field.getType(), Modifier.isFinal(field.getModifiers()), declaringClass);
+ artField = field.getArtField();
+ }
+
+ private FieldVarHandle(Field field) {
+ super(field.getType(), Modifier.isFinal(field.getModifiers()));
+ artField = field.getArtField();
+ }
+
+ static FieldVarHandle create(Field field) {
+ if (Modifier.isStatic(field.getModifiers())) {
+ return new FieldVarHandle(field);
+ } else {
+ return new FieldVarHandle(field, field.getDeclaringClass());
+ }
+ }
+}
diff --git a/java/lang/invoke/MethodHandle.java b/java/lang/invoke/MethodHandle.java
index af3db103..159f9dd7 100644
--- a/java/lang/invoke/MethodHandle.java
+++ b/java/lang/invoke/MethodHandle.java
@@ -25,1353 +25,28 @@
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;
- return new Transformers.VarargsCollector(this);
- }
+ public MethodType type() { 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 invokeExact(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 final Object invoke(Object... args) 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(Object... arguments) throws Throwable { return null; }
- return new Transformers.BindTo(this, x);
- }
+ public Object invokeWithArguments(java.util.List<?> arguments) throws Throwable { 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 asType(MethodType newType) { return null; }
- /** @hide */
- public int getHandleKind() {
- return handleKind;
- }
+ public MethodHandle asCollector(Class<?> arrayType, int arrayLength) { return null; }
- /** @hide */
- protected void transform(EmulatedStackFrame arguments) throws Throwable {
- throw new AssertionError("MethodHandle.transform should never be called.");
- }
+ public MethodHandle asVarargsCollector(Class<?> arrayType) { return null; }
- /**
- * 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 boolean isVarargsCollector() { return false; }
+ public MethodHandle asFixedArity() { 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);
- }
+ public MethodHandle bindTo(Object x) { return null; }
- // 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 88ce6e08..f27ad988 100644
--- a/java/lang/invoke/MethodHandles.java
+++ b/java/lang/invoke/MethodHandles.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 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
@@ -25,3410 +25,129 @@
package java.lang.invoke;
-import java.lang.reflect.*;
-import java.nio.ByteOrder;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
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 {
- 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 lookup() { return null; }
- //// Method handle creation from ordinary methods.
+ public static Lookup publicLookup() { return null; }
- /**
- * 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) {
- 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());
- }
+ reflectAs(Class<T> expected, MethodHandle target) { return null; }
- /**
- * 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 {
- /** 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 PUBLIC = 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 PRIVATE = 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 PROTECTED = 0;
- // Make sure outer class is initialized first.
- //
- // Android-changed: Removed unnecessary reference to IMPL_NAMES.
- // static { IMPL_NAMES.getClass(); }
+ public static final int PACKAGE = 0;
- /** 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 Class<?> lookupClass() { return null; }
- /** Package-private version of lookup which is trusted. */
- static final Lookup IMPL_LOOKUP = new Lookup(Object.class, ALL_MODES);
+ public int lookupModes() { return 0; }
- private static void checkUnprivilegedlookupClass(Class<?> lookupClass, int allowedModes) {
- String name = lookupClass.getName();
- if (name.startsWith("java.lang.invoke."))
- throw newIllegalArgumentException("illegal lookupClass: "+lookupClass);
+ public Lookup in(Class<?> requestedLookupClass) { return null; }
- // 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 {
- 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);
- }
+ MethodHandle findStatic(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 findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
- if (constructor.isVarArgs()) {
- mh = new Transformers.VarargsCollector(mh);
- }
- return mh;
- }
+ public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
- 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 {
- 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;
+ Class<?> specialCaller) throws NoSuchMethodException, 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 findGetter(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 findSetter(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 findStaticGetter(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 findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
- if (target instanceof MethodHandleImpl) {
- return (MethodHandleImpl) target;
- }
+ public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
- throw new IllegalArgumentException(target + " is not a direct handle");
- }
+ public MethodHandle unreflect(Method m) 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 unreflectSpecial(Method m, Class<?> specialCaller) 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 unreflectConstructor(Constructor<?> c) throws IllegalAccessException { return null; }
- return new Transformers.ReferenceArrayElementGetter(arrayClass);
- }
+ public MethodHandle unreflectGetter(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 MethodHandle unreflectSetter(Field f) throws IllegalAccessException { 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);
- }
+ public MethodHandleInfo revealDirect(MethodHandle target) { return null; }
- 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
- VarHandle byteArrayViewVarHandle(Class<?> viewArrayClass,
- ByteOrder byteOrder) throws IllegalArgumentException {
- unsupported("MethodHandles.byteArrayViewVarHandle()"); // TODO(b/65872996)
- return null;
- }
+ MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException { 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
- 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 */);
- }
+ MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException { return null; }
- /**
- * 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 invoker(MethodType type) {
- return new Transformers.Invoker(type, false /* isExactInvoker */);
- }
+ MethodHandle spreadInvoker(MethodType type, int leadingArgCount) { 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}.
- *
- * @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 varHandleExactInvoker(VarHandle.AccessMode accessMode, MethodType type) {
- unsupported("MethodHandles.varHandleExactInvoker()"); // TODO(b/65872996)
- return null;
- }
+ MethodHandle exactInvoker(MethodType type) { 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 varHandleInvoker(VarHandle.AccessMode accessMode, MethodType type) {
- unsupported("MethodHandles.varHandleInvoker()"); // TODO(b/65872996)
- return null;
- }
+ MethodHandle invoker(MethodType type) { 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) {
- 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);
- }
+ MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) { return null; }
- 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) {
- reorder = reorder.clone(); // get a private copy
- MethodType oldType = target.type();
- permuteArgumentChecks(reorder, newType, oldType);
-
- return new Transformers.PermuteArguments(newType, target, reorder);
- }
+ MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) { return null; }
- // 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) {
- if (type.isPrimitive()) {
- if (type == void.class)
- throw newIllegalArgumentException("void type");
- Wrapper w = Wrapper.forPrimitiveType(type);
- value = w.convert(value, type);
- }
+ MethodHandle constant(Class<?> type, Object value) { return null; }
- 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) {
- 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);
- }
- }
+ MethodHandle identity(Class<?> type) { return null; }
- 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) {
- int insCount = values.length;
- Class<?>[] ptypes = insertArgumentsChecks(target, insCount, pos);
- if (insCount == 0) {
- return target;
- }
+ MethodHandle insertArguments(MethodHandle target, int pos, Object... values) { return null; }
- // 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) {
- 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;
- }
+ MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) { return null; }
- /**
- * 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 dropArguments(target, pos, Arrays.asList(valueTypes));
- }
+ MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) { return null; }
- /**
- * 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) {
- 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 filterArguments(MethodHandle target, int pos, MethodHandle... filters) { return null; }
-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) {
- MethodType newType = collectArgumentsChecks(target, pos, filter);
- return new Transformers.CollectArguments(target, filter, pos, newType);
- }
+ MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) { return null; }
- 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) {
- MethodType targetType = target.type();
- MethodType filterType = filter.type();
- filterReturnValueChecks(targetType, filterType);
+ MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) { return null; }
- 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) {
- int foldPos = 0;
- MethodType targetType = target.type();
- MethodType combinerType = combiner.type();
- Class<?> rtype = foldArgumentChecks(foldPos, targetType, combinerType);
-
- return new Transformers.FoldArguments(target, combiner);
- }
+ MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) { return null; }
- 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) {
- 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);
- }
+ MethodHandle fallback) { return null; }
- /**
- * 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) {
- 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);
- }
+ MethodHandle handler) { return null; }
- /**
- * 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) {
- if (!Throwable.class.isAssignableFrom(exType))
- throw new ClassCastException(exType.getName());
-
- return new Transformers.AlwaysThrow(returnType, exType);
- }
+ MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) { return null; }
}
diff --git a/java/lang/invoke/MethodType.java b/java/lang/invoke/MethodType.java
index bfa7ccd5..4cb5c226 100644
--- a/java/lang/invoke/MethodType.java
+++ b/java/lang/invoke/MethodType.java
@@ -25,1227 +25,78 @@
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 makeImpl(rtype, ptypes, false);
+ return null;
}
- /**
- * 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) {
- boolean notrust = false; // random List impl. could return evil ptypes array
- return makeImpl(rtype, listToArray(ptypes), notrust);
+ return null;
}
- 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) {
- Class<?>[] ptypes1 = new Class<?>[1+ptypes.length];
- ptypes1[0] = ptype0;
- System.arraycopy(ptypes, 0, ptypes1, 1, ptypes.length);
- return makeImpl(rtype, ptypes1, true);
- }
+ MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) { return null; }
- /**
- * 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 makeImpl(rtype, NO_PTYPES, true);
- }
+ MethodType methodType(Class<?> rtype) { return null; }
- /**
- * 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 makeImpl(rtype, new Class<?>[]{ ptype0 }, true);
- }
+ MethodType methodType(Class<?> rtype, Class<?> ptype0) { return null; }
- /**
- * 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 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];
+ MethodType methodType(Class<?> rtype, MethodType ptypes) { return null; }
- /**
- * 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) {
- 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;
- }
+ MethodType genericMethodType(int objectArgCount, boolean finalArray) { return null; }
- /**
- * 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 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);
- }
+ MethodType genericMethodType(int objectArgCount) { 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 changeParameterType(int num, Class<?> nptype) { return null; }
- /*non-public*/ boolean isGeneric() {
- return this == erase() && !hasPrimitives();
- }
+ public MethodType insertParameterTypes(int num, 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 appendParameterTypes(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 insertParameterTypes(int num, 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 appendParameterTypes(List<Class<?>> ptypesToInsert) { 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 dropParameterTypes(int start, int end) { 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 MethodType changeReturnType(Class<?> nrtype) { return null; }
- /**
- * 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 hasPrimitives() { return false; }
- /*non-public*/ Class<?> lastParameterType() {
- int len = ptypes.length;
- return len == 0 ? void.class : ptypes[len-1];
- }
+ public boolean hasWrappers() { return false; }
- /**
- * 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 erase() { 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 generic() { return null; }
- private boolean equals(MethodType that) {
- return this.rtype == that.rtype
- && Arrays.equals(this.ptypes, that.ptypes);
- }
+ public MethodType wrap() { 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 MethodType unwrap() { 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 Class<?> parameterType(int num) { return null; }
- /** 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 int parameterCount() { return 0; }
- 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 Class<?> returnType() { 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 List<Class<?>> parameterList() { 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();
- }
+ public Class<?>[] parameterArray() { return null; }
- /// 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
- {
- 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);
- }
+ throws IllegalArgumentException, TypeNotPresentException { 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;
- }
-
- }
- }
+ public String toMethodDescriptorString() { return null; }
}
diff --git a/java/lang/invoke/VarHandle.java b/java/lang/invoke/VarHandle.java
index bb93fcf5..562efb6e 100644
--- a/java/lang/invoke/VarHandle.java
+++ b/java/lang/invoke/VarHandle.java
@@ -25,6 +25,10 @@
package java.lang.invoke;
+import dalvik.system.VMRuntime;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -442,9 +446,16 @@ public abstract class VarHandle {
*/
// END Android-removed: No VarForm in Android implementation.
- RuntimeException unsupported() {
- return new UnsupportedOperationException();
- }
+ // BEGIN Android-added: fields for common metadata.
+ /** The target type for accesses. */
+ private final Class<?> varType;
+
+ /** The coordinate types of a VarHandle instance. */
+ private final List<Class<?>> coordinateTypes;
+
+ /** BitMask of supported access mode indexed by AccessMode.ordinal(). */
+ private final int accessModesBitMask;
+ // END Android-added: fields for common metadata.
// Plain accessors
@@ -474,8 +485,8 @@ public abstract class VarHandle {
* symbolic type descriptor, but a reference cast fails.
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object get(Object... args);
@@ -501,8 +512,8 @@ public abstract class VarHandle {
* symbolic type descriptor, but a reference cast fails.
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
void set(Object... args);
@@ -534,8 +545,8 @@ public abstract class VarHandle {
* symbolic type descriptor, but a reference cast fails.
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getVolatile(Object... args);
@@ -565,8 +576,8 @@ public abstract class VarHandle {
* symbolic type descriptor, but a reference cast fails.
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
void setVolatile(Object... args);
@@ -596,8 +607,8 @@ public abstract class VarHandle {
* symbolic type descriptor, but a reference cast fails.
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getOpaque(Object... args);
@@ -624,8 +635,8 @@ public abstract class VarHandle {
* symbolic type descriptor, but a reference cast fails.
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
void setOpaque(Object... args);
@@ -662,8 +673,8 @@ public abstract class VarHandle {
* symbolic type descriptor, but a reference cast fails.
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAcquire(Object... args);
@@ -694,8 +705,8 @@ public abstract class VarHandle {
* symbolic type descriptor, but a reference cast fails.
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
void setRelease(Object... args);
@@ -731,8 +742,8 @@ public abstract class VarHandle {
* @see #getVolatile(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
boolean compareAndSet(Object... args);
@@ -767,8 +778,8 @@ public abstract class VarHandle {
* @see #getVolatile(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object compareAndExchange(Object... args);
@@ -803,8 +814,8 @@ public abstract class VarHandle {
* @see #getAcquire(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object compareAndExchangeAcquire(Object... args);
@@ -839,8 +850,8 @@ public abstract class VarHandle {
* @see #get(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object compareAndExchangeRelease(Object... args);
@@ -879,8 +890,8 @@ public abstract class VarHandle {
* @see #get(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
boolean weakCompareAndSetPlain(Object... args);
@@ -917,8 +928,8 @@ public abstract class VarHandle {
* @see #getVolatile(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
boolean weakCompareAndSet(Object... args);
@@ -956,8 +967,8 @@ public abstract class VarHandle {
* @see #getAcquire(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
boolean weakCompareAndSetAcquire(Object... args);
@@ -995,8 +1006,8 @@ public abstract class VarHandle {
* @see #get(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
boolean weakCompareAndSetRelease(Object... args);
@@ -1029,8 +1040,8 @@ public abstract class VarHandle {
* @see #getVolatile(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndSet(Object... args);
@@ -1063,8 +1074,8 @@ public abstract class VarHandle {
* @see #getVolatile(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndSetAcquire(Object... args);
@@ -1097,8 +1108,8 @@ public abstract class VarHandle {
* @see #getVolatile(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndSetRelease(Object... args);
@@ -1134,8 +1145,8 @@ public abstract class VarHandle {
* @see #getVolatile(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndAdd(Object... args);
@@ -1168,8 +1179,8 @@ public abstract class VarHandle {
* @see #getVolatile(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndAddAcquire(Object... args);
@@ -1202,8 +1213,8 @@ public abstract class VarHandle {
* @see #getVolatile(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndAddRelease(Object... args);
@@ -1244,8 +1255,8 @@ public abstract class VarHandle {
* @see #getVolatile(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndBitwiseOr(Object... args);
@@ -1282,8 +1293,8 @@ public abstract class VarHandle {
* @see #getAcquire(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndBitwiseOrAcquire(Object... args);
@@ -1320,8 +1331,8 @@ public abstract class VarHandle {
* @see #get(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndBitwiseOrRelease(Object... args);
@@ -1358,8 +1369,8 @@ public abstract class VarHandle {
* @see #getVolatile(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndBitwiseAnd(Object... args);
@@ -1396,8 +1407,8 @@ public abstract class VarHandle {
* @see #getAcquire(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndBitwiseAndAcquire(Object... args);
@@ -1434,8 +1445,8 @@ public abstract class VarHandle {
* @see #get(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndBitwiseAndRelease(Object... args);
@@ -1472,8 +1483,8 @@ public abstract class VarHandle {
* @see #getVolatile(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndBitwiseXor(Object... args);
@@ -1510,8 +1521,8 @@ public abstract class VarHandle {
* @see #getAcquire(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndBitwiseXorAcquire(Object... args);
@@ -1548,8 +1559,8 @@ public abstract class VarHandle {
* @see #get(Object...)
*/
public final native
- // Android-removed: unsupported annotations.
- // @MethodHandle.PolymorphicSignature
+ @MethodHandle.PolymorphicSignature
+ // Android-removed: unsupported annotation.
// @HotSpotIntrinsicCandidate
Object getAndBitwiseXorRelease(Object... args);
@@ -1559,7 +1570,11 @@ public abstract class VarHandle {
SET(void.class),
COMPARE_AND_SWAP(boolean.class),
COMPARE_AND_EXCHANGE(Object.class),
- GET_AND_UPDATE(Object.class);
+ GET_AND_UPDATE(Object.class),
+ // Android-added: Finer grained access types.
+ // These are used to help categorize the access modes that a VarHandle supports.
+ GET_AND_UPDATE_BITWISE(Object.class),
+ GET_AND_UPDATE_NUMERIC(Object.class);
final Class<?> returnType;
final boolean isMonomorphicInReturnType;
@@ -1596,6 +1611,8 @@ public abstract class VarHandle {
ps[i] = value;
return MethodType.methodType(value, ps);
case GET_AND_UPDATE:
+ case GET_AND_UPDATE_BITWISE:
+ case GET_AND_UPDATE_NUMERIC:
ps = allocateParameters(1, receiver, intermediate);
i = fillParameters(ps, receiver, intermediate);
ps[i] = value;
@@ -1746,73 +1763,73 @@ public abstract class VarHandle {
* method
* {@link VarHandle#getAndAdd VarHandle.getAndAdd}
*/
- GET_AND_ADD("getAndAdd", AccessType.GET_AND_UPDATE),
+ GET_AND_ADD("getAndAdd", AccessType.GET_AND_UPDATE_NUMERIC),
/**
* 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),
+ GET_AND_ADD_ACQUIRE("getAndAddAcquire", AccessType.GET_AND_UPDATE_NUMERIC),
/**
* 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),
+ GET_AND_ADD_RELEASE("getAndAddRelease", AccessType.GET_AND_UPDATE_NUMERIC),
/**
* 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),
+ GET_AND_BITWISE_OR("getAndBitwiseOr", AccessType.GET_AND_UPDATE_BITWISE),
/**
* 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),
+ GET_AND_BITWISE_OR_RELEASE("getAndBitwiseOrRelease", AccessType.GET_AND_UPDATE_BITWISE),
/**
* 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),
+ GET_AND_BITWISE_OR_ACQUIRE("getAndBitwiseOrAcquire", AccessType.GET_AND_UPDATE_BITWISE),
/**
* 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),
+ GET_AND_BITWISE_AND("getAndBitwiseAnd", AccessType.GET_AND_UPDATE_BITWISE),
/**
* 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),
+ GET_AND_BITWISE_AND_RELEASE("getAndBitwiseAndRelease", AccessType.GET_AND_UPDATE_BITWISE),
/**
* 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),
+ GET_AND_BITWISE_AND_ACQUIRE("getAndBitwiseAndAcquire", AccessType.GET_AND_UPDATE_BITWISE),
/**
* 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),
+ GET_AND_BITWISE_XOR("getAndBitwiseXor", AccessType.GET_AND_UPDATE_BITWISE),
/**
* 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),
+ GET_AND_BITWISE_XOR_RELEASE("getAndBitwiseXorRelease", AccessType.GET_AND_UPDATE_BITWISE),
/**
* 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),
+ GET_AND_BITWISE_XOR_ACQUIRE("getAndBitwiseXorAcquire", AccessType.GET_AND_UPDATE_BITWISE),
;
static final Map<String, AccessMode> methodNameToAccessMode;
@@ -1898,8 +1915,11 @@ public abstract class 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);
+ // Android-removed: existing implementation.
+ // MethodType typeSet = accessModeType(AccessMode.SET);
+ // return typeSet.parameterType(typeSet.parameterCount() - 1)
+ // Android-added: return instance field.
+ return varType;
}
/**
@@ -1909,8 +1929,11 @@ public abstract class VarHandle {
* list is unmodifiable
*/
public final List<Class<?>> coordinateTypes() {
- MethodType typeGet = accessModeType(AccessMode.GET);
- return typeGet.parameterList();
+ // Android-removed: existing implementation.
+ // MethodType typeGet = accessModeType(AccessMode.GET);
+ // return typeGet.parameterList();
+ // Android-added: return instance field.
+ return coordinateTypes;
}
/**
@@ -1940,9 +1963,18 @@ public abstract class VarHandle {
*/
// 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-added: alternative implementation.
+ switch (coordinateTypes.size()) {
+ case 0:
+ return accessMode.at.accessModeType(null, varType);
+ case 1:
+ return accessMode.at.accessModeType(coordinateTypes.get(0), varType);
+ case 2:
+ return accessMode.at.accessModeType(coordinateTypes.get(0), varType,
+ coordinateTypes.get(1));
+ default:
+ throw new InternalError("bad coordinateTypes: " + coordinateTypes);
+ }
}
// Android-removed: Not part of the Android implementation.
@@ -1964,9 +1996,9 @@ public abstract class VarHandle {
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;
+ // Android-added: use accessModesBitsMask field.
+ final int testBit = 1 << accessMode.ordinal();
+ return (accessModesBitMask & testBit) == testBit;
}
/**
@@ -2002,9 +2034,10 @@ public abstract class VarHandle {
bindTo(this);
}
*/
- // Android-added: Throw an exception until implemented.
- unsupported(); // TODO(b/65872996)
- return null;
+ // END Android-removed: no vform field in Android implementation.
+
+ // Android-added: basic implementation following description in javadoc for this method.
+ return MethodHandles.varHandleInvoker(accessMode, accessModeType(accessMode)).bindTo(this);
}
// BEGIN Android-removed: Not used in Android implementation.
@@ -2055,8 +2088,8 @@ public abstract class VarHandle {
*/
// END Android-removed: Not used in Android implementation.
- /*non-public*/
// BEGIN Android-removed: No VarForm in Android implementation.
+ /*non-public*/
/*
final void updateVarForm(VarForm newVForm) {
if (vform == newVForm) return;
@@ -2158,4 +2191,197 @@ public abstract class VarHandle {
// NB The compiler recognizes all the fences here as intrinsics.
UNSAFE.storeFence();
}
+
+ // BEGIN Android-added: package private constructors.
+ /**
+ * Constructor for VarHandle with no coordinates.
+ *
+ * @param varType the variable type of variables to be referenced
+ * @param isFinal whether the target variables are final (non-modifiable)
+ * @hide
+ */
+ VarHandle(Class<?> varType, boolean isFinal) {
+ this.varType = varType;
+ this.coordinateTypes = Collections.EMPTY_LIST;
+ this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal);
+ }
+
+ /**
+ * Constructor for VarHandle with one coordinate.
+ *
+ * @param varType the variable type of variables to be referenced
+ * @param isFinal whether the target variables are final (non-modifiable)
+ * @param coordinate the coordinate
+ * @hide
+ */
+ VarHandle(Class<?> varType, boolean isFinal, Class<?> coordinate) {
+ this.varType = varType;
+ this.coordinateTypes = Collections.singletonList(coordinate);
+ this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal);
+ }
+
+ /**
+ * Constructor for VarHandle with two coordinates.
+ *
+ * @param varType the variable type of variables to be referenced
+ * @param backingArrayType the type of the array accesses will be performed on
+ * @param isFinal whether the target variables are final (non-modifiable)
+ * @param coordinate0 the first coordinate
+ * @param coordinate1 the second coordinate
+ * @hide
+ */
+ VarHandle(Class<?> varType, Class<?> backingArrayType, boolean isFinal,
+ Class<?> coordinate0, Class<?> coordinate1) {
+ this.varType = varType;
+ this.coordinateTypes = Collections.unmodifiableList(
+ Arrays.asList(coordinate0, coordinate1));
+ Class<?> backingArrayComponentType = backingArrayType.getComponentType();
+ if (backingArrayComponentType != varType && backingArrayComponentType != byte.class) {
+ throw new InternalError("Unsupported backingArrayType: " + backingArrayType);
+ }
+
+ if (backingArrayType.getComponentType() == varType) {
+ this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal);
+ } else {
+ this.accessModesBitMask = unalignedAccessModesBitMask(varType);
+ }
+ }
+ // END Android-changed: package private constructors.
+
+ // BEGIN Android-added: helper state for VarHandle properties.
+
+ /** BitMask of access modes that do not change the memory referenced by a VarHandle.
+ * An example being a read of a variable with volatile ordering effects. */
+ private final static int READ_ACCESS_MODES_BIT_MASK;
+
+ /** BitMask of access modes that write to the memory referenced by
+ * a VarHandle. This does not include any compare and update
+ * access modes, nor any bitwise or numeric access modes. An
+ * example being a write to variable with release ordering
+ * effects.
+ */
+ private final static int WRITE_ACCESS_MODES_BIT_MASK;
+
+ /** BitMask of access modes that are applicable to types
+ * supporting for atomic updates. This includes access modes that
+ * both read and write a variable such as compare-and-set.
+ */
+ private final static int ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+
+ /** BitMask of access modes that are applicable to types
+ * supporting numeric atomic update operations. */
+ private final static int NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+
+ /** BitMask of access modes that are applicable to types
+ * supporting bitwise atomic update operations. */
+ private final static int BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+
+ /** BitMask of all access modes. */
+ private final static int ALL_MODES_BIT_MASK;
+
+ /** Indicator of machine word size. */
+ private final static boolean RUNNING_ON_64BIT = VMRuntime.getRuntime().is64Bit();
+
+ static {
+ // Check we're not about to overflow the storage of the
+ // bitmasks here and in the accessModesBitMask field.
+ if (AccessMode.values().length > Integer.SIZE) {
+ throw new InternalError("accessModes overflow");
+ }
+
+ // Access modes bit mask declarations and initialization order
+ // follows the presentation order in JEP193.
+ READ_ACCESS_MODES_BIT_MASK = accessTypesToBitMask(EnumSet.of(AccessType.GET));
+
+ WRITE_ACCESS_MODES_BIT_MASK = accessTypesToBitMask(EnumSet.of(AccessType.SET));
+
+ ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK =
+ accessTypesToBitMask(EnumSet.of(AccessType.COMPARE_AND_EXCHANGE,
+ AccessType.COMPARE_AND_SWAP,
+ AccessType.GET_AND_UPDATE));
+
+ NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK =
+ accessTypesToBitMask(EnumSet.of(AccessType.GET_AND_UPDATE_NUMERIC));
+
+ BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK =
+ accessTypesToBitMask(EnumSet.of(AccessType.GET_AND_UPDATE_BITWISE));
+
+ ALL_MODES_BIT_MASK = (READ_ACCESS_MODES_BIT_MASK |
+ WRITE_ACCESS_MODES_BIT_MASK |
+ ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK |
+ NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK |
+ BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK);
+ }
+
+ static int accessTypesToBitMask(final EnumSet<AccessType> accessTypes) {
+ int m = 0;
+ for (AccessMode accessMode : AccessMode.values()) {
+ if (accessTypes.contains(accessMode.at)) {
+ m |= 1 << accessMode.ordinal();
+ }
+ }
+ return m;
+ }
+
+ static int alignedAccessModesBitMask(Class<?> varType, boolean isFinal) {
+ // For aligned accesses, the supported access modes are described in:
+ // @see java.lang.invoke.MethodHandles.Lookup#findVarHandle
+ int bitMask = ALL_MODES_BIT_MASK;
+
+ // If the field is declared final, keep only the read access modes.
+ if (isFinal) {
+ bitMask &= READ_ACCESS_MODES_BIT_MASK;
+ }
+
+ // If the field is anything other than byte, short, char, int,
+ // long, float, double then remove the numeric atomic update
+ // access modes.
+ if (varType != byte.class && varType != short.class && varType != char.class &&
+ varType != int.class && varType != long.class
+ && varType != float.class && varType != double.class) {
+ bitMask &= ~NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+ }
+
+ // If the field is not integral, remove the bitwise atomic update access modes.
+ if (varType != boolean.class && varType != byte.class && varType != short.class &&
+ varType != char.class && varType != int.class && varType != long.class) {
+ bitMask &= ~BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+ }
+ return bitMask;
+ }
+
+ static int unalignedAccessModesBitMask(Class<?> varType) {
+ // The VarHandle refers to a view of byte array or a
+ // view of a byte buffer. The corresponding accesses
+ // maybe unaligned so the access modes are more
+ // restrictive than field or array element accesses.
+ //
+ // The supported access modes are described in:
+ // @see java.lang.invoke.MethodHandles#byteArrayViewVarHandle
+ int bitMask = 0;
+
+ // Read/write access modes supported for all types except for
+ // long and double on 32-bit platforms.
+ if (RUNNING_ON_64BIT || (varType != long.class && varType != double.class)) {
+ bitMask |= READ_ACCESS_MODES_BIT_MASK | WRITE_ACCESS_MODES_BIT_MASK;
+ }
+
+ // int, long, float, double support atomic update modes per documentation.
+ if (varType == int.class || varType == long.class ||
+ varType == float.class || varType == double.class) {
+ bitMask |= ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+ }
+
+ // int and long support numeric updates per documentation.
+ if (varType == int.class || varType == long.class) {
+ bitMask |= NUMERIC_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+ }
+
+ // int and long support bitwise updates per documentation.
+ if (varType == int.class || varType == long.class) {
+ bitMask |= BITWISE_ATOMIC_UPDATE_ACCESS_MODES_BIT_MASK;
+ }
+ return bitMask;
+ }
+ // END Android-added: helper class for VarHandle properties.
}
diff --git a/java/net/Socket.java b/java/net/Socket.java
index 03e2b717..e36d15b2 100644
--- a/java/net/Socket.java
+++ b/java/net/Socket.java
@@ -122,7 +122,7 @@ class Socket implements java.io.Closeable {
Proxy p = proxy == Proxy.NO_PROXY ? Proxy.NO_PROXY
: sun.net.ApplicationProxy.create(proxy);
Proxy.Type type = p.type();
- // Android-changed: Removed HTTP proxy suppport.
+ // Android-changed: Removed HTTP proxy support.
// if (type == Proxy.Type.SOCKS || type == Proxy.Type.HTTP) {
if (type == Proxy.Type.SOCKS) {
SecurityManager security = System.getSecurityManager();
@@ -214,6 +214,7 @@ class Socket implements java.io.Closeable {
public Socket(String host, int port)
throws UnknownHostException, IOException
{
+ // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
this(InetAddress.getAllByName(host), port, (SocketAddress) null, true);
}
@@ -245,6 +246,7 @@ class Socket implements java.io.Closeable {
* @see SecurityManager#checkConnect
*/
public Socket(InetAddress address, int port) throws IOException {
+ // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
this(nonNullAddress(address), port, (SocketAddress) null, true);
}
@@ -286,6 +288,7 @@ class Socket implements java.io.Closeable {
*/
public Socket(String host, int port, InetAddress localAddr,
int localPort) throws IOException {
+ // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
this(InetAddress.getAllByName(host), port,
new InetSocketAddress(localAddr, localPort), true);
}
@@ -327,6 +330,7 @@ class Socket implements java.io.Closeable {
*/
public Socket(InetAddress address, int port, InetAddress localAddr,
int localPort) throws IOException {
+ // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
this(nonNullAddress(address), port,
new InetSocketAddress(localAddr, localPort), true);
}
@@ -374,6 +378,7 @@ class Socket implements java.io.Closeable {
*/
@Deprecated
public Socket(String host, int port, boolean stream) throws IOException {
+ // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
this(InetAddress.getAllByName(host), port, (SocketAddress) null, stream);
}
@@ -415,9 +420,11 @@ class Socket implements java.io.Closeable {
*/
@Deprecated
public Socket(InetAddress host, int port, boolean stream) throws IOException {
+ // Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
this(nonNullAddress(host), port, new InetSocketAddress(0), stream);
}
+ // BEGIN Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
private static InetAddress[] nonNullAddress(InetAddress address) {
// backward compatibility
if (address == null)
@@ -426,8 +433,6 @@ class Socket implements java.io.Closeable {
return new InetAddress[] { address };
}
- // Android-changed: Socket ctor should try all addresses
- // b/30007735
private Socket(InetAddress[] addresses, int port, SocketAddress localAddr,
boolean stream) throws IOException {
if (addresses == null || addresses.length == 0) {
@@ -446,9 +451,8 @@ class Socket implements java.io.Closeable {
break;
} catch (IOException | IllegalArgumentException | SecurityException e) {
try {
- // Android-changed:
- // Do not call #close, classes that extend this class may do not expect a call
- // to #close coming from the superclass constructor.
+ // Android-changed: Let ctor call impl.close() instead of overridable close().
+ // Subclasses may not expect a call to close() coming from this constructor.
impl.close();
closed = true;
} catch (IOException ce) {
@@ -468,6 +472,7 @@ class Socket implements java.io.Closeable {
closed = false;
}
}
+ // END Android-changed: App compat. Socket ctor should try all addresses. http://b/30007735
/**
* Creates the socket implementation.
@@ -1060,7 +1065,7 @@ class Socket implements java.io.Closeable {
*
* The setting only affects socket close.
*
- * @return the setting for SO_LINGER.
+ * @return the setting for {@link SocketOptions#SO_LINGER SO_LINGER}.
* @exception SocketException if there is an error
* in the underlying protocol, such as a TCP error.
* @since JDK1.1
@@ -1768,7 +1773,7 @@ class Socket implements java.io.Closeable {
/* Not implemented yet */
}
- // Android-added: for testing and internal use.
+ // Android-added: getFileDescriptor$() method for testing and internal use.
/**
* @hide internal use only
*/
diff --git a/java/net/SocketException.java b/java/net/SocketException.java
index 286bc427..64ae7710 100644
--- a/java/net/SocketException.java
+++ b/java/net/SocketException.java
@@ -54,6 +54,7 @@ class SocketException extends IOException {
public SocketException() {
}
+ // BEGIN Android-added: SocketException ctor with cause for internal use.
/** @hide */
public SocketException(Throwable cause) {
super(cause);
@@ -63,4 +64,5 @@ class SocketException extends IOException {
public SocketException(String msg, Throwable cause) {
super(msg, cause);
}
+ // END Android-added: SocketException ctor with cause for internal use.
}
diff --git a/java/net/SocketImpl.java b/java/net/SocketImpl.java
index c0db070e..ade2630f 100644
--- a/java/net/SocketImpl.java
+++ b/java/net/SocketImpl.java
@@ -227,6 +227,7 @@ public abstract class SocketImpl implements SocketOptions {
return fd;
}
+ // Android-added: getFD$() for testing.
/**
* @hide used by java.nio tests
*/
diff --git a/java/net/SocketInputStream.java b/java/net/SocketInputStream.java
index f5c4c8f8..8d0e0c57 100644
--- a/java/net/SocketInputStream.java
+++ b/java/net/SocketInputStream.java
@@ -44,6 +44,11 @@ import sun.net.ConnectionResetException;
*/
class SocketInputStream extends FileInputStream
{
+ // Android-removed: Android doesn't need to call native init.
+ // static {
+ // init();
+ //}
+
private boolean eof;
private AbstractPlainSocketImpl impl = null;
private byte temp[];
@@ -166,6 +171,7 @@ class SocketInputStream extends FileInputStream
// acquire file descriptor and do the read
FileDescriptor fd = impl.acquireFD();
try {
+ // Android-added: Check BlockGuard policy in read().
BlockGuard.getThreadPolicy().onNetwork();
n = socketRead(fd, b, off, length, timeout);
if (n > 0) {
@@ -289,4 +295,11 @@ class SocketInputStream extends FileInputStream
* Overrides finalize, the fd is closed by the Socket.
*/
protected void finalize() {}
+
+ // Android-removed: Android doesn't need native init.
+ /*
+ * Perform class load-time initializations.
+ *
+ private native static void init();
+ */
}
diff --git a/java/net/SocketOutputStream.java b/java/net/SocketOutputStream.java
index c0173f0d..9e8e7926 100644
--- a/java/net/SocketOutputStream.java
+++ b/java/net/SocketOutputStream.java
@@ -43,6 +43,11 @@ import dalvik.system.BlockGuard;
*/
class SocketOutputStream extends FileOutputStream
{
+ // Android-removed: Android doesn't need to call native init.
+ // static {
+ // init();
+ //}
+
private AbstractPlainSocketImpl impl = null;
private byte temp[] = new byte[1];
private Socket socket = null;
@@ -95,6 +100,8 @@ class SocketOutputStream extends FileOutputStream
* @exception IOException If an I/O error has occurred.
*/
private void socketWrite(byte b[], int off, int len) throws IOException {
+
+
if (len <= 0 || off < 0 || len > b.length - off) {
if (len == 0) {
return;
@@ -105,6 +112,7 @@ class SocketOutputStream extends FileOutputStream
FileDescriptor fd = impl.acquireFD();
try {
+ // Android-added: Check BlockGuard policy in socketWrite.
BlockGuard.getThreadPolicy().onNetwork();
socketWrite0(fd, b, off, len);
} catch (SocketException se) {
@@ -174,4 +182,11 @@ class SocketOutputStream extends FileOutputStream
* Overrides finalize, the fd is closed by the Socket.
*/
protected void finalize() {}
+
+ // Android-removed: Android doesn't need native init.
+ /*
+ * Perform class load-time initializations.
+ *
+ private native static void init();
+ */
}
diff --git a/java/net/SocksSocketImpl.java b/java/net/SocksSocketImpl.java
index a81e219b..0d9d8f59 100644
--- a/java/net/SocksSocketImpl.java
+++ b/java/net/SocksSocketImpl.java
@@ -347,12 +347,91 @@ class SocksSocketImpl extends PlainSocketImpl implements SocksConsts {
epoint.getPort());
}
if (server == null) {
- // Android-removed: Logic to establish proxy connection based on default ProxySelector
+ // Android-removed: Logic to establish proxy connection based on default ProxySelector.
+ // Removed code that tried to establish proxy connection if ProxySelector#getDefault()
+ // is not null. This was never the case in previous Android releases, was causing
+ // issues and therefore was removed.
/*
- * Removed code that tried to establish proxy connection if
- * ProxySelector#getDefault() is not null.
- * This was never the case in previous android releases, was causing
- * issues and therefore was removed.
+ // This is the general case
+ // server is not null only when the socket was created with a
+ // specified proxy in which case it does bypass the ProxySelector
+ ProxySelector sel = java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction<ProxySelector>() {
+ public ProxySelector run() {
+ return ProxySelector.getDefault();
+ }
+ });
+ if (sel == null) {
+ /*
+ * No default proxySelector --> direct connection
+ *
+ super.connect(epoint, remainingMillis(deadlineMillis));
+ return;
+ }
+ URI uri;
+ // Use getHostString() to avoid reverse lookups
+ String host = epoint.getHostString();
+ // IPv6 litteral?
+ if (epoint.getAddress() instanceof Inet6Address &&
+ (!host.startsWith("[")) && (host.indexOf(":") >= 0)) {
+ host = "[" + host + "]";
+ }
+ try {
+ uri = new URI("socket://" + ParseUtil.encodePath(host) + ":"+ epoint.getPort());
+ } catch (URISyntaxException e) {
+ // This shouldn't happen
+ assert false : e;
+ uri = null;
+ }
+ Proxy p = null;
+ IOException savedExc = null;
+ java.util.Iterator<Proxy> iProxy = null;
+ iProxy = sel.select(uri).iterator();
+ if (iProxy == null || !(iProxy.hasNext())) {
+ super.connect(epoint, remainingMillis(deadlineMillis));
+ return;
+ }
+ while (iProxy.hasNext()) {
+ p = iProxy.next();
+ if (p == null || p.type() != Proxy.Type.SOCKS) {
+ super.connect(epoint, remainingMillis(deadlineMillis));
+ return;
+ }
+
+ if (!(p.address() instanceof InetSocketAddress))
+ throw new SocketException("Unknown address type for proxy: " + p);
+ // Use getHostString() to avoid reverse lookups
+ server = ((InetSocketAddress) p.address()).getHostString();
+ serverPort = ((InetSocketAddress) p.address()).getPort();
+ if (p instanceof SocksProxy) {
+ if (((SocksProxy)p).protocolVersion() == 4) {
+ useV4 = true;
+ }
+ }
+
+ // Connects to the SOCKS server
+ try {
+ privilegedConnect(server, serverPort, remainingMillis(deadlineMillis));
+ // Worked, let's get outta here
+ break;
+ } catch (IOException e) {
+ // Ooops, let's notify the ProxySelector
+ sel.connectFailed(uri,p.address(),e);
+ server = null;
+ serverPort = -1;
+ savedExc = e;
+ // Will continue the while loop and try the next proxy
+ }
+ }
+
+ /*
+ * If server is still null at this point, none of the proxy
+ * worked
+ *
+ if (server == null) {
+ throw new SocketException("Can't connect to SOCKS proxy:"
+ + savedExc.getMessage());
+ }
*/
super.connect(epoint, remainingMillis(deadlineMillis));
return;
@@ -508,6 +587,458 @@ class SocksSocketImpl extends PlainSocketImpl implements SocksConsts {
external_address = epoint;
}
+ // Android-removed: Dead code. bindV4, socksBind, acceptFrom methods.
+ /*
+ private void bindV4(InputStream in, OutputStream out,
+ InetAddress baddr,
+ int lport) throws IOException {
+ if (!(baddr instanceof Inet4Address)) {
+ throw new SocketException("SOCKS V4 requires IPv4 only addresses");
+ }
+ super.bind(baddr, lport);
+ byte[] addr1 = baddr.getAddress();
+ /* Test for AnyLocal *
+ InetAddress naddr = baddr;
+ if (naddr.isAnyLocalAddress()) {
+ naddr = AccessController.doPrivileged(
+ new PrivilegedAction<InetAddress>() {
+ public InetAddress run() {
+ return cmdsock.getLocalAddress();
+
+ }
+ });
+ addr1 = naddr.getAddress();
+ }
+ out.write(PROTO_VERS4);
+ out.write(BIND);
+ out.write((super.getLocalPort() >> 8) & 0xff);
+ out.write((super.getLocalPort() >> 0) & 0xff);
+ out.write(addr1);
+ String userName = getUserName();
+ try {
+ out.write(userName.getBytes("ISO-8859-1"));
+ } catch (java.io.UnsupportedEncodingException uee) {
+ assert false;
+ }
+ out.write(0);
+ out.flush();
+ byte[] data = new byte[8];
+ int n = readSocksReply(in, data);
+ if (n != 8)
+ throw new SocketException("Reply from SOCKS server has bad length: " + n);
+ if (data[0] != 0 && data[0] != 4)
+ throw new SocketException("Reply from SOCKS server has bad version");
+ SocketException ex = null;
+ switch (data[1]) {
+ case 90:
+ // Success!
+ external_address = new InetSocketAddress(baddr, lport);
+ break;
+ case 91:
+ ex = new SocketException("SOCKS request rejected");
+ break;
+ case 92:
+ ex = new SocketException("SOCKS server couldn't reach destination");
+ break;
+ case 93:
+ ex = new SocketException("SOCKS authentication failed");
+ break;
+ default:
+ ex = new SocketException("Reply from SOCKS server contains bad status");
+ break;
+ }
+ if (ex != null) {
+ in.close();
+ out.close();
+ throw ex;
+ }
+
+ }
+
+ /**
+ * Sends the Bind request to the SOCKS proxy. In the SOCKS protocol, bind
+ * means "accept incoming connection from", so the SocketAddress is the
+ * the one of the host we do accept connection from.
+ *
+ * @param saddr the Socket address of the remote host.
+ * @exception IOException if an I/O error occurs when binding this socket.
+ *
+ protected synchronized void socksBind(InetSocketAddress saddr) throws IOException {
+ if (socket != null) {
+ // this is a client socket, not a server socket, don't
+ // call the SOCKS proxy for a bind!
+ return;
+ }
+
+ // Connects to the SOCKS server
+
+ if (server == null) {
+ // This is the general case
+ // server is not null only when the socket was created with a
+ // specified proxy in which case it does bypass the ProxySelector
+ ProxySelector sel = java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction<ProxySelector>() {
+ public ProxySelector run() {
+ return ProxySelector.getDefault();
+ }
+ });
+ if (sel == null) {
+ /*
+ * No default proxySelector --> direct connection
+ *
+ return;
+ }
+ URI uri;
+ // Use getHostString() to avoid reverse lookups
+ String host = saddr.getHostString();
+ // IPv6 litteral?
+ if (saddr.getAddress() instanceof Inet6Address &&
+ (!host.startsWith("[")) && (host.indexOf(":") >= 0)) {
+ host = "[" + host + "]";
+ }
+ try {
+ uri = new URI("serversocket://" + ParseUtil.encodePath(host) + ":"+ saddr.getPort());
+ } catch (URISyntaxException e) {
+ // This shouldn't happen
+ assert false : e;
+ uri = null;
+ }
+ Proxy p = null;
+ Exception savedExc = null;
+ java.util.Iterator<Proxy> iProxy = null;
+ iProxy = sel.select(uri).iterator();
+ if (iProxy == null || !(iProxy.hasNext())) {
+ return;
+ }
+ while (iProxy.hasNext()) {
+ p = iProxy.next();
+ if (p == null || p.type() != Proxy.Type.SOCKS) {
+ return;
+ }
+
+ if (!(p.address() instanceof InetSocketAddress))
+ throw new SocketException("Unknown address type for proxy: " + p);
+ // Use getHostString() to avoid reverse lookups
+ server = ((InetSocketAddress) p.address()).getHostString();
+ serverPort = ((InetSocketAddress) p.address()).getPort();
+ if (p instanceof SocksProxy) {
+ if (((SocksProxy)p).protocolVersion() == 4) {
+ useV4 = true;
+ }
+ }
+
+ // Connects to the SOCKS server
+ try {
+ AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Void>() {
+ public Void run() throws Exception {
+ cmdsock = new Socket(new PlainSocketImpl());
+ cmdsock.connect(new InetSocketAddress(server, serverPort));
+ cmdIn = cmdsock.getInputStream();
+ cmdOut = cmdsock.getOutputStream();
+ return null;
+ }
+ });
+ } catch (Exception e) {
+ // Ooops, let's notify the ProxySelector
+ sel.connectFailed(uri,p.address(),new SocketException(e.getMessage()));
+ server = null;
+ serverPort = -1;
+ cmdsock = null;
+ savedExc = e;
+ // Will continue the while loop and try the next proxy
+ }
+ }
+
+ /*
+ * If server is still null at this point, none of the proxy
+ * worked
+ *
+ if (server == null || cmdsock == null) {
+ throw new SocketException("Can't connect to SOCKS proxy:"
+ + savedExc.getMessage());
+ }
+ } else {
+ try {
+ AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Void>() {
+ public Void run() throws Exception {
+ cmdsock = new Socket(new PlainSocketImpl());
+ cmdsock.connect(new InetSocketAddress(server, serverPort));
+ cmdIn = cmdsock.getInputStream();
+ cmdOut = cmdsock.getOutputStream();
+ return null;
+ }
+ });
+ } catch (Exception e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+ BufferedOutputStream out = new BufferedOutputStream(cmdOut, 512);
+ InputStream in = cmdIn;
+ if (useV4) {
+ bindV4(in, out, saddr.getAddress(), saddr.getPort());
+ return;
+ }
+ out.write(PROTO_VERS);
+ out.write(2);
+ out.write(NO_AUTH);
+ out.write(USER_PASSW);
+ out.flush();
+ byte[] data = new byte[2];
+ int i = readSocksReply(in, data);
+ if (i != 2 || ((int)data[0]) != PROTO_VERS) {
+ // Maybe it's not a V5 sever after all
+ // Let's try V4 before we give up
+ bindV4(in, out, saddr.getAddress(), saddr.getPort());
+ return;
+ }
+ if (((int)data[1]) == NO_METHODS)
+ throw new SocketException("SOCKS : No acceptable methods");
+ if (!authenticate(data[1], in, out)) {
+ throw new SocketException("SOCKS : authentication failed");
+ }
+ // We're OK. Let's issue the BIND command.
+ out.write(PROTO_VERS);
+ out.write(BIND);
+ out.write(0);
+ int lport = saddr.getPort();
+ if (saddr.isUnresolved()) {
+ out.write(DOMAIN_NAME);
+ out.write(saddr.getHostName().length());
+ try {
+ out.write(saddr.getHostName().getBytes("ISO-8859-1"));
+ } catch (java.io.UnsupportedEncodingException uee) {
+ assert false;
+ }
+ out.write((lport >> 8) & 0xff);
+ out.write((lport >> 0) & 0xff);
+ } else if (saddr.getAddress() instanceof Inet4Address) {
+ byte[] addr1 = saddr.getAddress().getAddress();
+ out.write(IPV4);
+ out.write(addr1);
+ out.write((lport >> 8) & 0xff);
+ out.write((lport >> 0) & 0xff);
+ out.flush();
+ } else if (saddr.getAddress() instanceof Inet6Address) {
+ byte[] addr1 = saddr.getAddress().getAddress();
+ out.write(IPV6);
+ out.write(addr1);
+ out.write((lport >> 8) & 0xff);
+ out.write((lport >> 0) & 0xff);
+ out.flush();
+ } else {
+ cmdsock.close();
+ throw new SocketException("unsupported address type : " + saddr);
+ }
+ data = new byte[4];
+ i = readSocksReply(in, data);
+ SocketException ex = null;
+ int len, nport;
+ byte[] addr;
+ switch (data[1]) {
+ case REQUEST_OK:
+ // success!
+ switch(data[3]) {
+ case IPV4:
+ addr = new byte[4];
+ i = readSocksReply(in, addr);
+ if (i != 4)
+ throw new SocketException("Reply from SOCKS server badly formatted");
+ data = new byte[2];
+ i = readSocksReply(in, data);
+ if (i != 2)
+ throw new SocketException("Reply from SOCKS server badly formatted");
+ nport = ((int)data[0] & 0xff) << 8;
+ nport += ((int)data[1] & 0xff);
+ external_address =
+ new InetSocketAddress(new Inet4Address("", addr) , nport);
+ break;
+ case DOMAIN_NAME:
+ len = data[1];
+ byte[] host = new byte[len];
+ i = readSocksReply(in, host);
+ if (i != len)
+ throw new SocketException("Reply from SOCKS server badly formatted");
+ data = new byte[2];
+ i = readSocksReply(in, data);
+ if (i != 2)
+ throw new SocketException("Reply from SOCKS server badly formatted");
+ nport = ((int)data[0] & 0xff) << 8;
+ nport += ((int)data[1] & 0xff);
+ external_address = new InetSocketAddress(new String(host), nport);
+ break;
+ case IPV6:
+ len = data[1];
+ addr = new byte[len];
+ i = readSocksReply(in, addr);
+ if (i != len)
+ throw new SocketException("Reply from SOCKS server badly formatted");
+ data = new byte[2];
+ i = readSocksReply(in, data);
+ if (i != 2)
+ throw new SocketException("Reply from SOCKS server badly formatted");
+ nport = ((int)data[0] & 0xff) << 8;
+ nport += ((int)data[1] & 0xff);
+ external_address =
+ new InetSocketAddress(new Inet6Address("", addr), nport);
+ break;
+ }
+ break;
+ case GENERAL_FAILURE:
+ ex = new SocketException("SOCKS server general failure");
+ break;
+ case NOT_ALLOWED:
+ ex = new SocketException("SOCKS: Bind not allowed by ruleset");
+ break;
+ case NET_UNREACHABLE:
+ ex = new SocketException("SOCKS: Network unreachable");
+ break;
+ case HOST_UNREACHABLE:
+ ex = new SocketException("SOCKS: Host unreachable");
+ break;
+ case CONN_REFUSED:
+ ex = new SocketException("SOCKS: Connection refused");
+ break;
+ case TTL_EXPIRED:
+ ex = new SocketException("SOCKS: TTL expired");
+ break;
+ case CMD_NOT_SUPPORTED:
+ ex = new SocketException("SOCKS: Command not supported");
+ break;
+ case ADDR_TYPE_NOT_SUP:
+ ex = new SocketException("SOCKS: address type not supported");
+ break;
+ }
+ if (ex != null) {
+ in.close();
+ out.close();
+ cmdsock.close();
+ cmdsock = null;
+ throw ex;
+ }
+ cmdIn = in;
+ cmdOut = out;
+ }
+
+ /**
+ * Accepts a connection from a specific host.
+ *
+ * @param s the accepted connection.
+ * @param saddr the socket address of the host we do accept
+ * connection from
+ * @exception IOException if an I/O error occurs when accepting the
+ * connection.
+ *
+ protected void acceptFrom(SocketImpl s, InetSocketAddress saddr) throws IOException {
+ if (cmdsock == null) {
+ // Not a Socks ServerSocket.
+ return;
+ }
+ InputStream in = cmdIn;
+ // Sends the "SOCKS BIND" request.
+ socksBind(saddr);
+ in.read();
+ int i = in.read();
+ in.read();
+ SocketException ex = null;
+ int nport;
+ byte[] addr;
+ InetSocketAddress real_end = null;
+ switch (i) {
+ case REQUEST_OK:
+ // success!
+ i = in.read();
+ switch(i) {
+ case IPV4:
+ addr = new byte[4];
+ readSocksReply(in, addr);
+ nport = in.read() << 8;
+ nport += in.read();
+ real_end =
+ new InetSocketAddress(new Inet4Address("", addr) , nport);
+ break;
+ case DOMAIN_NAME:
+ int len = in.read();
+ addr = new byte[len];
+ readSocksReply(in, addr);
+ nport = in.read() << 8;
+ nport += in.read();
+ real_end = new InetSocketAddress(new String(addr), nport);
+ break;
+ case IPV6:
+ addr = new byte[16];
+ readSocksReply(in, addr);
+ nport = in.read() << 8;
+ nport += in.read();
+ real_end =
+ new InetSocketAddress(new Inet6Address("", addr), nport);
+ break;
+ }
+ break;
+ case GENERAL_FAILURE:
+ ex = new SocketException("SOCKS server general failure");
+ break;
+ case NOT_ALLOWED:
+ ex = new SocketException("SOCKS: Accept not allowed by ruleset");
+ break;
+ case NET_UNREACHABLE:
+ ex = new SocketException("SOCKS: Network unreachable");
+ break;
+ case HOST_UNREACHABLE:
+ ex = new SocketException("SOCKS: Host unreachable");
+ break;
+ case CONN_REFUSED:
+ ex = new SocketException("SOCKS: Connection refused");
+ break;
+ case TTL_EXPIRED:
+ ex = new SocketException("SOCKS: TTL expired");
+ break;
+ case CMD_NOT_SUPPORTED:
+ ex = new SocketException("SOCKS: Command not supported");
+ break;
+ case ADDR_TYPE_NOT_SUP:
+ ex = new SocketException("SOCKS: address type not supported");
+ break;
+ }
+ if (ex != null) {
+ cmdIn.close();
+ cmdOut.close();
+ cmdsock.close();
+ cmdsock = null;
+ throw ex;
+ }
+
+ /**
+ * This is where we have to do some fancy stuff.
+ * The datastream from the socket "accepted" by the proxy will
+ * come through the cmdSocket. So we have to swap the socketImpls
+ *
+ if (s instanceof SocksSocketImpl) {
+ ((SocksSocketImpl)s).external_address = real_end;
+ }
+ if (s instanceof PlainSocketImpl) {
+ PlainSocketImpl psi = (PlainSocketImpl) s;
+ psi.setInputStream((SocketInputStream) in);
+ psi.setFileDescriptor(cmdsock.getImpl().getFileDescriptor());
+ psi.setAddress(cmdsock.getImpl().getInetAddress());
+ psi.setPort(cmdsock.getImpl().getPort());
+ psi.setLocalPort(cmdsock.getImpl().getLocalPort());
+ } else {
+ s.fd = cmdsock.getImpl().fd;
+ s.address = cmdsock.getImpl().address;
+ s.port = cmdsock.getImpl().port;
+ s.localport = cmdsock.getImpl().localport;
+ }
+
+ // Need to do that so that the socket won't be closed
+ // when the ServerSocket is closed by the user.
+ // It kinds of detaches the Socket because it is now
+ // used elsewhere.
+ cmdsock = null;
+ }
+ */
+
/**
* Returns the value of this socket's {@code address} field.
*
diff --git a/java/net/URLClassLoader.java b/java/net/URLClassLoader.java
index edb9b882..6e8acb1c 100644
--- a/java/net/URLClassLoader.java
+++ b/java/net/URLClassLoader.java
@@ -103,8 +103,8 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
if (security != null) {
security.checkCreateClassLoader();
}
- ucp = new URLClassPath(urls);
this.acc = AccessController.getContext();
+ ucp = new URLClassPath(urls, acc);
}
URLClassLoader(URL[] urls, ClassLoader parent,
@@ -115,8 +115,8 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
if (security != null) {
security.checkCreateClassLoader();
}
- ucp = new URLClassPath(urls);
this.acc = acc;
+ ucp = new URLClassPath(urls, acc);
}
/**
@@ -147,8 +147,8 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
if (security != null) {
security.checkCreateClassLoader();
}
- ucp = new URLClassPath(urls);
this.acc = AccessController.getContext();
+ ucp = new URLClassPath(urls, acc);
}
URLClassLoader(URL[] urls, AccessControlContext acc) {
@@ -158,8 +158,8 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
if (security != null) {
security.checkCreateClassLoader();
}
- ucp = new URLClassPath(urls);
this.acc = acc;
+ ucp = new URLClassPath(urls, acc);
}
/**
@@ -180,6 +180,7 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
* @exception SecurityException if a security manager exists and its
* {@code checkCreateClassLoader} method doesn't allow
* creation of a class loader.
+ * @exception NullPointerException if {@code urls} is {@code null}.
* @see SecurityManager#checkCreateClassLoader
*/
public URLClassLoader(URL[] urls, ClassLoader parent,
@@ -271,13 +272,13 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
* and errors are not caught. Calling close on an already closed
* loader has no effect.
* <p>
- * @throws IOException if closing any file opened by this class loader
+ * @exception IOException if closing any file opened by this class loader
* resulted in an IOException. Any such exceptions are caught internally.
* If only one is caught, then it is re-thrown. If more than one exception
* is caught, then the second and following exceptions are added
* as suppressed exceptions of the first one caught, which is then re-thrown.
*
- * @throws SecurityException if a security manager is set, and it denies
+ * @exception SecurityException if a security manager is set, and it denies
* {@link RuntimePermission}{@code ("closeClassLoader")}
*
* @since 1.7
@@ -455,12 +456,16 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
// Use (direct) ByteBuffer:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
+ // Android-removed: Android doesn't use sun.misc.PerfCounter.
+ // sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, bb, cs);
} else {
byte[] b = res.getBytes();
// must read certificates AFTER reading bytes.
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
+ // Android-removed: Android doesn't use sun.misc.PerfCounter.
+ // sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, b, 0, b.length, cs);
}
}
@@ -766,6 +771,7 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
}
static {
+ // Android-removed: SharedSecrets.setJavaNetAccess call. Android doesn't use it.
/*sun.misc.SharedSecrets.setJavaNetAccess (
new sun.misc.JavaNetAccess() {
public URLClassPath getURLClassPath (URLClassLoader u) {
diff --git a/java/security/AlgorithmParameters.java b/java/security/AlgorithmParameters.java
index 36bb3ee0..864866ef 100644
--- a/java/security/AlgorithmParameters.java
+++ b/java/security/AlgorithmParameters.java
@@ -29,6 +29,8 @@ import java.io.*;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
+import sun.security.jca.Providers;
+
/**
* This class is used as an opaque representation of cryptographic parameters.
*
@@ -285,6 +287,8 @@ public class AlgorithmParameters {
{
if (provider == null || provider.length() == 0)
throw new IllegalArgumentException("missing provider");
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "AlgorithmParameters", algorithm);
Object[] objs = Security.getImpl(algorithm, "AlgorithmParameters",
provider);
return new AlgorithmParameters((AlgorithmParametersSpi)objs[0],
@@ -330,6 +334,8 @@ public class AlgorithmParameters {
{
if (provider == null)
throw new IllegalArgumentException("missing provider");
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "AlgorithmParameters", algorithm);
Object[] objs = Security.getImpl(algorithm, "AlgorithmParameters",
provider);
return new AlgorithmParameters((AlgorithmParametersSpi)objs[0],
diff --git a/java/security/KeyFactory.java b/java/security/KeyFactory.java
index a6b912c5..d01ce161 100644
--- a/java/security/KeyFactory.java
+++ b/java/security/KeyFactory.java
@@ -231,6 +231,8 @@ public class KeyFactory {
*/
public static KeyFactory getInstance(String algorithm, String provider)
throws NoSuchAlgorithmException, NoSuchProviderException {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "KeyFactory", algorithm);
Instance instance = GetInstance.getInstance("KeyFactory",
KeyFactorySpi.class, algorithm, provider);
return new KeyFactory((KeyFactorySpi)instance.impl,
@@ -268,6 +270,8 @@ public class KeyFactory {
*/
public static KeyFactory getInstance(String algorithm, Provider provider)
throws NoSuchAlgorithmException {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "KeyFactory", algorithm);
Instance instance = GetInstance.getInstance("KeyFactory",
KeyFactorySpi.class, algorithm, provider);
return new KeyFactory((KeyFactorySpi)instance.impl,
diff --git a/java/security/KeyPairGenerator.java b/java/security/KeyPairGenerator.java
index 68ab5e94..51a0ec93 100644
--- a/java/security/KeyPairGenerator.java
+++ b/java/security/KeyPairGenerator.java
@@ -299,6 +299,8 @@ public abstract class KeyPairGenerator extends KeyPairGeneratorSpi {
public static KeyPairGenerator getInstance(String algorithm,
String provider)
throws NoSuchAlgorithmException, NoSuchProviderException {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "KeyPairGenerator", algorithm);
Instance instance = GetInstance.getInstance("KeyPairGenerator",
KeyPairGeneratorSpi.class, algorithm, provider);
return getInstance(instance, algorithm);
@@ -335,6 +337,8 @@ public abstract class KeyPairGenerator extends KeyPairGeneratorSpi {
*/
public static KeyPairGenerator getInstance(String algorithm,
Provider provider) throws NoSuchAlgorithmException {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "KeyPairGenerator", algorithm);
Instance instance = GetInstance.getInstance("KeyPairGenerator",
KeyPairGeneratorSpi.class, algorithm, provider);
return getInstance(instance, algorithm);
diff --git a/java/security/MessageDigest.java b/java/security/MessageDigest.java
index 8e5dab14..ab2614a5 100644
--- a/java/security/MessageDigest.java
+++ b/java/security/MessageDigest.java
@@ -35,6 +35,8 @@ import java.io.ByteArrayInputStream;
import java.nio.ByteBuffer;
+import sun.security.jca.Providers;
+
/**
* This MessageDigest class provides applications the functionality of a
* message digest algorithm, such as SHA-1 or SHA-256.
@@ -255,6 +257,8 @@ public abstract class MessageDigest extends MessageDigestSpi {
{
if (provider == null || provider.length() == 0)
throw new IllegalArgumentException("missing provider");
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "MessageDigest", algorithm);
Object[] objs = Security.getImpl(algorithm, "MessageDigest", provider);
if (objs[0] instanceof MessageDigest) {
MessageDigest md = (MessageDigest)objs[0];
@@ -303,6 +307,8 @@ public abstract class MessageDigest extends MessageDigestSpi {
{
if (provider == null)
throw new IllegalArgumentException("missing provider");
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "MessageDigest", algorithm);
Object[] objs = Security.getImpl(algorithm, "MessageDigest", provider);
if (objs[0] instanceof MessageDigest) {
MessageDigest md = (MessageDigest)objs[0];
diff --git a/java/security/Signature.java b/java/security/Signature.java
index 5a0e6a87..9deaf564 100644
--- a/java/security/Signature.java
+++ b/java/security/Signature.java
@@ -498,6 +498,8 @@ public abstract class Signature extends SignatureSpi {
}
return getInstanceRSA(p);
}
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "Signature", algorithm);
Instance instance = GetInstance.getInstance
("Signature", SignatureSpi.class, algorithm, provider);
return getInstance(instance, algorithm);
@@ -541,6 +543,8 @@ public abstract class Signature extends SignatureSpi {
}
return getInstanceRSA(provider);
}
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "Signature", algorithm);
Instance instance = GetInstance.getInstance
("Signature", SignatureSpi.class, algorithm, provider);
return getInstance(instance, algorithm);
diff --git a/java/security/cert/CertificateFactory.java b/java/security/cert/CertificateFactory.java
index e74ff0a2..5ccbb333 100644
--- a/java/security/cert/CertificateFactory.java
+++ b/java/security/cert/CertificateFactory.java
@@ -250,6 +250,8 @@ public class CertificateFactory {
String provider) throws CertificateException,
NoSuchProviderException {
try {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "CertificateFactory", type);
Instance instance = GetInstance.getInstance("CertificateFactory",
CertificateFactorySpi.class, type, provider);
return new CertificateFactory((CertificateFactorySpi)instance.impl,
@@ -291,6 +293,8 @@ public class CertificateFactory {
public static final CertificateFactory getInstance(String type,
Provider provider) throws CertificateException {
try {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "CertificateFactory", type);
Instance instance = GetInstance.getInstance("CertificateFactory",
CertificateFactorySpi.class, type, provider);
return new CertificateFactory((CertificateFactorySpi)instance.impl,
diff --git a/javax/crypto/KeyAgreement.java b/javax/crypto/KeyAgreement.java
index ffa18b1c..ce42de8a 100644
--- a/javax/crypto/KeyAgreement.java
+++ b/javax/crypto/KeyAgreement.java
@@ -252,6 +252,8 @@ public class KeyAgreement {
public static final KeyAgreement getInstance(String algorithm,
String provider) throws NoSuchAlgorithmException,
NoSuchProviderException {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "KeyAgreement", algorithm);
Instance instance = JceSecurity.getInstance
("KeyAgreement", KeyAgreementSpi.class, algorithm, provider);
return new KeyAgreement((KeyAgreementSpi)instance.impl,
@@ -292,6 +294,8 @@ public class KeyAgreement {
*/
public static final KeyAgreement getInstance(String algorithm,
Provider provider) throws NoSuchAlgorithmException {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "KeyAgreement", algorithm);
Instance instance = JceSecurity.getInstance
("KeyAgreement", KeyAgreementSpi.class, algorithm, provider);
return new KeyAgreement((KeyAgreementSpi)instance.impl,
diff --git a/javax/crypto/KeyGenerator.java b/javax/crypto/KeyGenerator.java
index 5dfde971..b0977f0a 100644
--- a/javax/crypto/KeyGenerator.java
+++ b/javax/crypto/KeyGenerator.java
@@ -326,6 +326,8 @@ public class KeyGenerator {
public static final KeyGenerator getInstance(String algorithm,
String provider) throws NoSuchAlgorithmException,
NoSuchProviderException {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "KeyGenerator", algorithm);
Instance instance = JceSecurity.getInstance("KeyGenerator",
KeyGeneratorSpi.class, algorithm, provider);
return new KeyGenerator((KeyGeneratorSpi)instance.impl,
@@ -364,6 +366,8 @@ public class KeyGenerator {
*/
public static final KeyGenerator getInstance(String algorithm,
Provider provider) throws NoSuchAlgorithmException {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "KeyGenerator", algorithm);
Instance instance = JceSecurity.getInstance("KeyGenerator",
KeyGeneratorSpi.class, algorithm, provider);
return new KeyGenerator((KeyGeneratorSpi)instance.impl,
diff --git a/javax/crypto/Mac.java b/javax/crypto/Mac.java
index c3d99fb3..dab6971c 100644
--- a/javax/crypto/Mac.java
+++ b/javax/crypto/Mac.java
@@ -309,6 +309,8 @@ public class Mac implements Cloneable {
*/
public static final Mac getInstance(String algorithm, String provider)
throws NoSuchAlgorithmException, NoSuchProviderException {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "Mac", algorithm);
Instance instance = JceSecurity.getInstance
("Mac", MacSpi.class, algorithm, provider);
return new Mac((MacSpi)instance.impl, instance.provider, algorithm);
@@ -344,6 +346,8 @@ public class Mac implements Cloneable {
*/
public static final Mac getInstance(String algorithm, Provider provider)
throws NoSuchAlgorithmException {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "Mac", algorithm);
Instance instance = JceSecurity.getInstance
("Mac", MacSpi.class, algorithm, provider);
return new Mac((MacSpi)instance.impl, instance.provider, algorithm);
diff --git a/javax/crypto/SecretKeyFactory.java b/javax/crypto/SecretKeyFactory.java
index c1358c20..be1cef47 100644
--- a/javax/crypto/SecretKeyFactory.java
+++ b/javax/crypto/SecretKeyFactory.java
@@ -385,6 +385,8 @@ public class SecretKeyFactory {
public static final SecretKeyFactory getInstance(String algorithm,
String provider) throws NoSuchAlgorithmException,
NoSuchProviderException {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "SecretKeyFactory", algorithm);
Instance instance = JceSecurity.getInstance("SecretKeyFactory",
SecretKeyFactorySpi.class, algorithm, provider);
return new SecretKeyFactory((SecretKeyFactorySpi)instance.impl,
@@ -425,6 +427,8 @@ public class SecretKeyFactory {
*/
public static final SecretKeyFactory getInstance(String algorithm,
Provider provider) throws NoSuchAlgorithmException {
+ // Android-added: Check for Bouncy Castle deprecation
+ Providers.checkBouncyCastleDeprecation(provider, "SecretKeyFactory", algorithm);
Instance instance = JceSecurity.getInstance("SecretKeyFactory",
SecretKeyFactorySpi.class, algorithm, provider);
return new SecretKeyFactory((SecretKeyFactorySpi)instance.impl,