summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-11-30 18:18:21 -0500
committerJustin Klaassen <justinklaassen@google.com>2017-11-30 18:18:21 -0500
commit4217cf85c20565a3446a662a7f07f26137b26b7f (patch)
treea0417b47a8cc802f6642f369fd2371165bec7b5c
parent6a65f2da209bff03cb0eb6da309710ac6ee5026d (diff)
downloadandroid-28-4217cf85c20565a3446a662a7f07f26137b26b7f.tar.gz
Import Android SDK Platform P [4477446]
/google/data/ro/projects/android/fetch_artifact \ --bid 4477446 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4477446.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: If0559643d7c328e36aafca98f0c114641d33642c
-rw-r--r--android/accessibilityservice/AccessibilityService.java5
-rw-r--r--android/accessibilityservice/AccessibilityServiceInfo.java6
-rw-r--r--android/app/Activity.java2
-rw-r--r--android/app/ActivityManager.java8
-rw-r--r--android/app/ActivityManagerInternal.java16
-rw-r--r--android/app/ActivityThread.java439
-rw-r--r--android/app/ApplicationLoaders.java10
-rw-r--r--android/app/ApplicationPackageManager.java23
-rw-r--r--android/app/ClientTransactionHandler.java114
-rw-r--r--android/app/ContextImpl.java15
-rw-r--r--android/app/Notification.java25
-rw-r--r--android/app/NotificationManager.java66
-rw-r--r--android/app/ProfilerInfo.java29
-rw-r--r--android/app/ResultInfo.java27
-rw-r--r--android/app/SharedPreferencesImpl.java78
-rw-r--r--android/app/WindowConfiguration.java65
-rw-r--r--android/app/admin/ConnectEvent.java35
-rw-r--r--android/app/admin/DevicePolicyManager.java43
-rw-r--r--android/app/admin/DnsEvent.java51
-rw-r--r--android/app/admin/NetworkEvent.java31
-rw-r--r--android/app/assist/AssistStructure.java29
-rw-r--r--android/app/job/JobInfo.java35
-rw-r--r--android/app/job/JobService.java54
-rw-r--r--android/app/servertransaction/ActivityConfigurationChangeItem.java88
-rw-r--r--android/app/servertransaction/ActivityLifecycleItem.java44
-rw-r--r--android/app/servertransaction/ActivityResultItem.java95
-rw-r--r--android/app/servertransaction/BaseClientRequest.java45
-rw-r--r--android/app/servertransaction/ClientTransaction.java201
-rw-r--r--android/app/servertransaction/ClientTransactionItem.java54
-rw-r--r--android/app/servertransaction/ConfigurationChangeItem.java85
-rw-r--r--android/app/servertransaction/DestroyActivityItem.java99
-rw-r--r--android/app/servertransaction/LaunchActivityItem.java232
-rw-r--r--android/app/servertransaction/MoveToDisplayItem.java93
-rw-r--r--android/app/servertransaction/MultiWindowModeChangeItem.java92
-rw-r--r--android/app/servertransaction/NewIntentItem.java108
-rw-r--r--android/app/servertransaction/PauseActivityItem.java125
-rw-r--r--android/app/servertransaction/PipModeChangeItem.java89
-rw-r--r--android/app/servertransaction/ResumeActivityItem.java114
-rw-r--r--android/app/servertransaction/StopActivityItem.java112
-rw-r--r--android/app/servertransaction/WindowVisibilityItem.java85
-rw-r--r--android/app/slice/Slice.java205
-rw-r--r--android/app/slice/SliceItem.java187
-rw-r--r--android/app/slice/SliceProvider.java40
-rw-r--r--android/app/slice/SliceQuery.java55
-rw-r--r--android/app/slice/SliceSpec.java117
-rw-r--r--android/app/slice/widget/ActionRow.java201
-rw-r--r--android/app/slice/widget/GridView.java192
-rw-r--r--android/app/slice/widget/LargeSliceAdapter.java224
-rw-r--r--android/app/slice/widget/LargeTemplateView.java131
-rw-r--r--android/app/slice/widget/MessageView.java77
-rw-r--r--android/app/slice/widget/RemoteInputView.java445
-rw-r--r--android/app/slice/widget/ShortcutView.java175
-rw-r--r--android/app/slice/widget/SliceView.java422
-rw-r--r--android/app/slice/widget/SliceViewUtil.java198
-rw-r--r--android/app/slice/widget/SmallTemplateView.java211
-rw-r--r--android/app/usage/UsageEvents.java6
-rw-r--r--android/app/usage/UsageStatsManagerInternal.java14
-rw-r--r--android/arch/paging/BoundedDataSource.java80
-rw-r--r--android/arch/paging/ContiguousDataSource.java45
-rw-r--r--android/arch/paging/ContiguousPagedList.java86
-rw-r--r--android/arch/paging/DataSource.java166
-rw-r--r--android/arch/paging/KeyedDataSource.java447
-rw-r--r--android/arch/paging/ListDataSource.java23
-rw-r--r--android/arch/paging/LivePagedListBuilder.java110
-rw-r--r--android/arch/paging/Page.java51
-rw-r--r--android/arch/paging/PageResult.java58
-rw-r--r--android/arch/paging/PagedList.java206
-rw-r--r--android/arch/paging/PagedListAdapter.java10
-rw-r--r--android/arch/paging/PagedListAdapterHelper.java10
-rw-r--r--android/arch/paging/PagedStorage.java120
-rw-r--r--android/arch/paging/PagedStorageDiffHelper.java8
-rw-r--r--android/arch/paging/PositionalDataSource.java321
-rw-r--r--android/arch/paging/TiledDataSource.java196
-rw-r--r--android/arch/paging/TiledPagedList.java97
-rw-r--r--android/arch/paging/integration/testapp/ItemDataSource.java55
-rw-r--r--android/arch/paging/integration/testapp/PagedListItemViewModel.java32
-rw-r--r--android/arch/persistence/room/integration/testapp/CustomerViewModel.java11
-rw-r--r--android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java10
-rw-r--r--android/arch/persistence/room/integration/testapp/dao/UserDao.java6
-rw-r--r--android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java54
-rw-r--r--android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java6
-rw-r--r--android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java11
-rw-r--r--android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java7
-rw-r--r--android/bluetooth/BluetoothHidDevice.java36
-rw-r--r--android/bluetooth/BluetoothHidDeviceAppQosSettings.java103
-rw-r--r--android/bluetooth/BluetoothHidDeviceAppSdpSettings.java20
-rw-r--r--android/content/CursorLoader.java2
-rw-r--r--android/content/pm/ActivityInfo.java2
-rw-r--r--android/content/pm/ApplicationInfo.java28
-rw-r--r--android/content/pm/InstantAppInfo.java2
-rw-r--r--android/content/pm/PackageInfo.java28
-rw-r--r--android/content/pm/PackageInstaller.java3
-rw-r--r--android/content/pm/PackageManager.java278
-rw-r--r--android/content/pm/PackageManagerInternal.java8
-rw-r--r--android/content/pm/PackageParser.java123
-rw-r--r--android/content/pm/PermissionInfo.java18
-rw-r--r--android/database/sqlite/SQLiteConnectionPool.java10
-rw-r--r--android/database/sqlite/SQLiteDatabase.java12
-rw-r--r--android/graphics/Paint_Delegate.java11
-rw-r--r--android/hardware/camera2/CameraCharacteristics.java34
-rw-r--r--android/hardware/camera2/CameraMetadata.java112
-rw-r--r--android/hardware/camera2/CaptureRequest.java77
-rw-r--r--android/hardware/camera2/CaptureResult.java102
-rw-r--r--android/hardware/display/DisplayManager.java6
-rw-r--r--android/hardware/display/DisplayManagerInternal.java5
-rw-r--r--android/hardware/location/ContextHubClient.java62
-rw-r--r--android/hardware/location/ContextHubInfo.java247
-rw-r--r--android/hardware/location/ContextHubManager.java78
-rw-r--r--android/hardware/radio/RadioTuner.java68
-rw-r--r--android/hardware/radio/TunerAdapter.java19
-rw-r--r--android/hardware/radio/TunerCallbackAdapter.java7
-rw-r--r--android/hardware/usb/UsbManager.java11
-rw-r--r--android/inputmethodservice/KeyboardView.java9
-rw-r--r--android/media/ImageReader.java1
-rw-r--r--android/media/MediaDrm.java68
-rw-r--r--android/media/MediaFormat.java8
-rw-r--r--android/media/MediaMetadata.java66
-rw-r--r--android/media/MediaMuxer.java16
-rw-r--r--android/media/MediaRecorder.java165
-rw-r--r--android/media/tv/TvInputManager.java4
-rw-r--r--android/net/IpSecAlgorithm.java62
-rw-r--r--android/net/IpSecConfig.java10
-rw-r--r--android/net/IpSecManager.java272
-rw-r--r--android/net/IpSecTransform.java177
-rw-r--r--android/net/NetworkCapabilities.java16
-rw-r--r--android/net/NetworkRequest.java7
-rw-r--r--android/net/NetworkState.java11
-rw-r--r--android/net/metrics/WakeupEvent.java4
-rw-r--r--android/net/metrics/WakeupStats.java3
-rw-r--r--android/net/wifi/BatchedScanResult.java2
-rw-r--r--android/net/wifi/WifiManager.java75
-rw-r--r--android/net/wifi/aware/DiscoverySessionCallback.java26
-rw-r--r--android/net/wifi/aware/PublishConfig.java56
-rw-r--r--android/net/wifi/aware/SubscribeConfig.java156
-rw-r--r--android/net/wifi/aware/WifiAwareManager.java47
-rw-r--r--android/net/wifi/hotspot2/ProvisioningCallback.java59
-rw-r--r--android/net/wifi/rtt/WifiRttManager.java2
-rw-r--r--android/os/BatteryManager.java9
-rw-r--r--android/os/BatteryStats.java159
-rw-r--r--android/os/Build.java87
-rw-r--r--android/os/GraphicsEnvironment.java106
-rw-r--r--android/os/HidlSupport.java5
-rw-r--r--android/os/Parcel.java93
-rw-r--r--android/os/ParcelPerfTest.java80
-rw-r--r--android/os/PowerManager.java17
-rw-r--r--android/os/Process.java3
-rw-r--r--android/os/RemoteException.java6
-rw-r--r--android/os/ShellCallback.java3
-rw-r--r--android/os/ShellCommand.java12
-rw-r--r--android/os/UEventObserver.java2
-rw-r--r--android/os/storage/StorageManager.java9
-rw-r--r--android/provider/SearchIndexableData.java2
-rw-r--r--android/provider/SearchIndexablesContract.java36
-rw-r--r--android/provider/SearchIndexablesProvider.java16
-rw-r--r--android/provider/Settings.java41
-rw-r--r--android/security/KeyChain.java15
-rw-r--r--android/service/autofill/AutofillService.java15
-rw-r--r--android/service/autofill/Dataset.java25
-rw-r--r--android/service/autofill/FillResponse.java23
-rw-r--r--android/service/autofill/InternalSanitizer.java5
-rw-r--r--android/service/autofill/NegationValidator.java79
-rw-r--r--android/service/autofill/SaveCallback.java37
-rw-r--r--android/service/autofill/SaveInfo.java11
-rw-r--r--android/service/autofill/TextValueSanitizer.java17
-rw-r--r--android/service/autofill/Validators.java13
-rw-r--r--android/service/euicc/EuiccService.java7
-rw-r--r--android/service/wallpaper/WallpaperService.java3
-rw-r--r--android/support/LibraryGroups.java1
-rw-r--r--android/support/car/widget/PagedListView.java32
-rw-r--r--android/support/design/widget/BottomSheetBehavior.java2
-rw-r--r--android/support/design/widget/CollapsingToolbarLayout.java1
-rw-r--r--android/support/design/widget/CoordinatorLayout.java2
-rw-r--r--android/support/design/widget/DirectedAcyclicGraphTest.java207
-rw-r--r--android/support/design/widget/FloatingActionButton.java38
-rw-r--r--android/support/design/widget/TextInputEditText.java7
-rw-r--r--android/support/design/widget/TextInputLayout.java5
-rw-r--r--android/support/doclava/DoclavaJavadocOptionFileOption.java75
-rw-r--r--android/support/graphics/drawable/VectorDrawableCompat.java4
-rw-r--r--android/support/mediacompat/testlib/MediaBrowserConstants.java10
-rw-r--r--android/support/mediacompat/testlib/MediaControllerConstants.java2
-rw-r--r--android/support/mediacompat/testlib/MediaSessionConstants.java2
-rw-r--r--android/support/mediacompat/testlib/VersionConstants.java3
-rw-r--r--android/support/mediacompat/testlib/util/IntentUtil.java9
-rw-r--r--android/support/text/emoji/EmojiCompat.java3
-rw-r--r--android/support/text/emoji/MetadataListReader.java2
-rw-r--r--android/support/text/emoji/widget/EmojiEditableFactory.java1
-rw-r--r--android/support/v17/leanback/widget/GridLayoutManager.java51
-rw-r--r--android/support/v4/graphics/TypefaceCompatApi26Impl.java5
-rw-r--r--android/support/v4/media/MediaBrowserCompat.java12
-rw-r--r--android/support/v4/media/MediaBrowserProtocol.java10
-rw-r--r--android/support/v4/media/MediaBrowserServiceCompat.java73
-rw-r--r--android/support/v4/view/NestedScrollingParent2.java3
-rw-r--r--android/support/v4/view/PagerAdapter.java1
-rw-r--r--android/support/v4/view/ViewPager.java4
-rw-r--r--android/support/v4/widget/DirectedAcyclicGraph.java (renamed from android/support/design/widget/DirectedAcyclicGraph.java)39
-rw-r--r--android/support/v4/widget/ViewGroupUtils.java (renamed from android/support/design/widget/ViewGroupUtils.java)15
-rw-r--r--android/support/v7/preference/CollapsiblePreferenceGroupController.java2
-rw-r--r--android/support/v7/util/SortedList.java277
-rw-r--r--android/support/v7/util/SortedListTest.java900
-rw-r--r--android/support/v7/view/ContextThemeWrapper.java22
-rw-r--r--android/support/v7/widget/AppCompatAutoCompleteTextView.java8
-rw-r--r--android/support/v7/widget/AppCompatCheckedTextView.java8
-rw-r--r--android/support/v7/widget/AppCompatEditText.java8
-rw-r--r--android/support/v7/widget/AppCompatHintHelper.java43
-rw-r--r--android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java8
-rw-r--r--android/support/v7/widget/AppCompatTextView.java8
-rw-r--r--android/support/v7/widget/RecyclerView.java120
-rw-r--r--android/support/v7/widget/Toolbar.java13
-rw-r--r--android/support/v7/widget/TooltipPopup.java18
-rw-r--r--android/support/v7/widget/WithHint.java36
-rw-r--r--android/support/wear/ambient/AmbientMode.java43
-rw-r--r--android/system/Os.java43
-rw-r--r--android/telecom/RemoteConnection.java2
-rw-r--r--android/telecom/TelecomManager.java2
-rw-r--r--android/telephony/CarrierConfigManager.java9
-rw-r--r--android/telephony/CellIdentityGsm.java22
-rw-r--r--android/telephony/CellIdentityLte.java22
-rw-r--r--android/telephony/CellIdentityWcdma.java22
-rw-r--r--android/telephony/SmsManager.java1
-rw-r--r--android/telephony/SubscriptionManager.java31
-rw-r--r--android/telephony/TelephonyManager.java86
-rw-r--r--android/telephony/data/DataProfile.java280
-rw-r--r--android/telephony/ims/ImsService.java418
-rw-r--r--android/telephony/ims/feature/ImsFeature.java31
-rw-r--r--android/telephony/ims/feature/MMTelFeature.java148
-rw-r--r--android/telephony/ims/feature/RcsFeature.java12
-rw-r--r--android/test/mock/MockPackageManager.java12
-rw-r--r--android/test/suitebuilder/TestSuiteBuilder.java4
-rw-r--r--android/text/StaticLayout_Delegate.java3
-rw-r--r--android/text/format/Formatter.java6
-rw-r--r--android/util/AndroidException.java6
-rw-r--r--android/util/FeatureFlagUtils.java22
-rw-r--r--android/util/Log.java295
-rw-r--r--android/util/StatsManager.java13
-rw-r--r--android/util/proto/ProtoUtils.java12
-rw-r--r--android/view/Display.java5
-rw-r--r--android/view/DisplayCutout.java408
-rw-r--r--android/view/Gravity.java33
-rw-r--r--android/view/InputFilter.java8
-rw-r--r--android/view/SurfaceControl.java54
-rw-r--r--android/view/SurfaceView.java6
-rw-r--r--android/view/View.java141
-rw-r--r--android/view/ViewGroup.java2
-rw-r--r--android/view/ViewRootImpl.java114
-rw-r--r--android/view/WindowInsets.java50
-rw-r--r--android/view/WindowManager.java6
-rw-r--r--android/view/WindowManagerPolicyConstants.java116
-rw-r--r--android/view/accessibility/AccessibilityCache.java7
-rw-r--r--android/view/accessibility/AccessibilityEvent.java41
-rw-r--r--android/view/accessibility/AccessibilityInteractionClient.java117
-rw-r--r--android/view/accessibility/AccessibilityManager.java16
-rw-r--r--android/view/accessibility/AccessibilityNodeInfo.java33
-rw-r--r--android/view/autofill/AutofillManager.java2
-rw-r--r--android/view/textclassifier/TextClassification.java13
-rw-r--r--android/view/textclassifier/TextClassifier.java146
-rw-r--r--android/view/textclassifier/TextClassifierImpl.java39
-rw-r--r--android/view/textclassifier/TextLinks.java54
-rw-r--r--android/view/textclassifier/TextSelection.java7
-rw-r--r--android/webkit/UserPackage.java4
-rw-r--r--android/webkit/WebSettings.java30
-rw-r--r--android/webkit/WebView.java4
-rw-r--r--android/webkit/WebViewClient.java14
-rw-r--r--android/widget/Editor.java51
-rw-r--r--android/widget/Magnifier.java221
-rw-r--r--android/widget/NumberPicker.java7
-rw-r--r--android/widget/RemoteViews.java25
-rw-r--r--android/widget/TextView.java4
-rw-r--r--android/widget/Toast.java3
-rw-r--r--androidx/app/slice/ArrayUtils.java75
-rw-r--r--androidx/app/slice/Slice.java420
-rw-r--r--androidx/app/slice/SliceConvert.java106
-rw-r--r--androidx/app/slice/SliceItem.java351
-rw-r--r--androidx/app/slice/SliceProvider.java130
-rw-r--r--androidx/app/slice/builders/MessagingSliceBuilder.java14
-rw-r--r--androidx/app/slice/builders/TemplateSliceBuilder.java3
-rw-r--r--androidx/app/slice/compat/ContentProviderWrapper.java121
-rw-r--r--androidx/app/slice/compat/SliceProviderCompat.java266
-rw-r--r--androidx/app/slice/compat/SliceProviderWrapper.java62
-rw-r--r--androidx/app/slice/core/SliceHints.java (renamed from androidx/app/slice/builders/SliceHints.java)12
-rw-r--r--androidx/app/slice/core/SliceQuery.java168
-rw-r--r--androidx/app/slice/core/SliceSpecs.java33
-rw-r--r--androidx/app/slice/widget/ActionRow.java81
-rw-r--r--androidx/app/slice/widget/GridView.java94
-rw-r--r--androidx/app/slice/widget/LargeSliceAdapter.java69
-rw-r--r--androidx/app/slice/widget/LargeTemplateView.java56
-rw-r--r--androidx/app/slice/widget/MessageView.java31
-rw-r--r--androidx/app/slice/widget/RemoteInputView.java2
-rw-r--r--androidx/app/slice/widget/ShortcutView.java47
-rw-r--r--androidx/app/slice/widget/SliceLiveData.java53
-rw-r--r--androidx/app/slice/widget/SliceView.java27
-rw-r--r--androidx/app/slice/widget/SliceViewUtil.java3
-rw-r--r--androidx/app/slice/widget/SmallTemplateView.java138
-rw-r--r--com/android/car/setupwizardlib/CarSetupWizardLayout.java22
-rw-r--r--com/android/commands/pm/Pm.java822
-rw-r--r--com/android/datetimepicker/AccessibleLinearLayout.java3
-rw-r--r--com/android/datetimepicker/AccessibleTextView.java3
-rw-r--r--com/android/datetimepicker/HapticFeedbackController.java3
-rw-r--r--com/android/datetimepicker/Utils.java3
-rw-r--r--com/android/datetimepicker/date/AccessibleDateAnimator.java2
-rw-r--r--com/android/datetimepicker/date/DatePickerController.java2
-rw-r--r--com/android/datetimepicker/date/DatePickerDialog.java3
-rw-r--r--com/android/datetimepicker/date/DayPickerView.java2
-rw-r--r--com/android/datetimepicker/date/MonthAdapter.java2
-rw-r--r--com/android/datetimepicker/date/MonthView.java2
-rw-r--r--com/android/datetimepicker/date/SimpleDayPickerView.java2
-rw-r--r--com/android/datetimepicker/date/SimpleMonthAdapter.java2
-rw-r--r--com/android/datetimepicker/date/SimpleMonthView.java2
-rw-r--r--com/android/datetimepicker/date/TextViewWithCircularIndicator.java3
-rw-r--r--com/android/datetimepicker/date/YearPickerView.java2
-rw-r--r--com/android/datetimepicker/time/AmPmCirclesView.java2
-rw-r--r--com/android/datetimepicker/time/CircleView.java2
-rw-r--r--com/android/datetimepicker/time/RadialPickerLayout.java2
-rw-r--r--com/android/datetimepicker/time/RadialSelectorView.java2
-rw-r--r--com/android/datetimepicker/time/RadialTextsView.java2
-rw-r--r--com/android/datetimepicker/time/TimePickerDialog.java3
-rw-r--r--com/android/ex/photo/ActionBarWrapper.java3
-rw-r--r--com/android/ex/photo/PhotoViewActivity.java8
-rw-r--r--com/android/ims/ImsManager.java59
-rw-r--r--com/android/ims/ImsServiceProxy.java58
-rw-r--r--com/android/internal/app/procstats/ProcessState.java1
-rw-r--r--com/android/internal/content/NativeLibraryHelper.java13
-rw-r--r--com/android/internal/content/PackageHelper.java9
-rw-r--r--com/android/internal/content/ReferrerIntent.java19
-rw-r--r--com/android/internal/os/BatteryStatsImpl.java41
-rw-r--r--com/android/internal/policy/DecorView.java6
-rw-r--r--com/android/internal/telephony/CarrierIdentifier.java569
-rw-r--r--com/android/internal/telephony/CarrierInfoManager.java72
-rw-r--r--com/android/internal/telephony/CommandsInterface.java2
-rw-r--r--com/android/internal/telephony/GsmCdmaPhone.java27
-rw-r--r--com/android/internal/telephony/Phone.java16
-rw-r--r--com/android/internal/telephony/RIL.java394
-rw-r--r--com/android/internal/telephony/RadioIndication.java17
-rw-r--r--com/android/internal/telephony/RadioResponse.java24
-rw-r--r--com/android/internal/telephony/ServiceStateTracker.java13
-rw-r--r--com/android/internal/telephony/SubscriptionInfoUpdater.java16
-rw-r--r--com/android/internal/telephony/TelephonyComponentFactory.java4
-rw-r--r--com/android/internal/telephony/cat/ComprehensionTlv.java2
-rw-r--r--com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java56
-rw-r--r--com/android/internal/telephony/dataconnection/ApnSetting.java21
-rw-r--r--com/android/internal/telephony/dataconnection/DataCallResponse.java195
-rw-r--r--com/android/internal/telephony/dataconnection/DataConnection.java186
-rw-r--r--com/android/internal/telephony/dataconnection/DataProfile.java133
-rw-r--r--com/android/internal/telephony/dataconnection/DcTracker.java26
-rw-r--r--com/android/internal/telephony/euicc/EuiccController.java17
-rw-r--r--com/android/internal/telephony/euicc/EuiccOperation.java4
-rw-r--r--com/android/internal/telephony/gsm/GsmMmiCode.java15
-rw-r--r--com/android/internal/telephony/ims/ImsResolver.java70
-rw-r--r--com/android/internal/telephony/ims/ImsServiceController.java149
-rw-r--r--com/android/internal/telephony/imsphone/ImsPhone.java6
-rw-r--r--com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java2
-rw-r--r--com/android/internal/telephony/sip/SipCommandInterface.java2
-rw-r--r--com/android/internal/telephony/test/SimulatedCommands.java13
-rw-r--r--com/android/internal/telephony/test/SimulatedCommandsVerifier.java2
-rw-r--r--com/android/internal/telephony/uicc/SIMRecords.java84
-rw-r--r--com/android/internal/telephony/uicc/SpnOverride.java114
-rw-r--r--com/android/internal/util/ArrayUtils.java8
-rw-r--r--com/android/internal/util/RingBuffer.java19
-rw-r--r--com/android/internal/util/UserIcons.java8
-rw-r--r--com/android/internal/view/TooltipPopup.java5
-rw-r--r--com/android/internal/widget/ExploreByTouchHelper.java12
-rw-r--r--com/android/internal/widget/Magnifier.java218
-rw-r--r--com/android/internal/widget/PointerLocationView.java2
-rw-r--r--com/android/internal/widget/RecyclerView.java2
-rw-r--r--com/android/keyguard/KeyguardSliceView.java14
-rw-r--r--com/android/keyguard/KeyguardUpdateMonitorCallback.java8
-rw-r--r--com/android/keyguard/PasswordTextView.java5
-rw-r--r--com/android/layoutlib/bridge/Bridge.java655
-rw-r--r--com/android/layoutlib/bridge/android/BridgePackageManager.java9
-rw-r--r--com/android/providers/settings/SettingsProtoDumpUtil.java12
-rw-r--r--com/android/providers/settings/SettingsProvider.java71
-rw-r--r--com/android/server/AlarmManagerService.java197
-rw-r--r--com/android/server/BatteryService.java1
-rw-r--r--com/android/server/BluetoothManagerService.java17
-rw-r--r--com/android/server/DeviceIdleController.java19
-rw-r--r--com/android/server/ForceAppStandbyTracker.java838
-rw-r--r--com/android/server/GestureLauncherService.java2
-rw-r--r--com/android/server/InputMethodManagerService.java3
-rw-r--r--com/android/server/LocationManagerService.java2
-rw-r--r--com/android/server/NetworkTimeUpdateService.java8
-rw-r--r--com/android/server/StorageManagerService.java23
-rw-r--r--com/android/server/SystemServer.java4
-rw-r--r--com/android/server/accessibility/AccessibilityClientConnection.java117
-rw-r--r--com/android/server/accessibility/AccessibilityInputFilter.java2
-rw-r--r--com/android/server/accessibility/AccessibilityManagerService.java19
-rw-r--r--com/android/server/accessibility/AccessibilityServiceConnection.java2
-rw-r--r--com/android/server/accessibility/GlobalActionPerformer.java16
-rw-r--r--com/android/server/accessibility/KeyEventDispatcher.java3
-rw-r--r--com/android/server/accessibility/KeyboardInterceptor.java3
-rw-r--r--com/android/server/accessibility/MagnificationController.java2
-rw-r--r--com/android/server/accessibility/MotionEventInjector.java2
-rw-r--r--com/android/server/accessibility/TouchExplorer.java5
-rw-r--r--com/android/server/accessibility/UiAutomationManager.java3
-rw-r--r--com/android/server/am/ActiveServices.java32
-rw-r--r--com/android/server/am/ActivityDisplay.java22
-rw-r--r--com/android/server/am/ActivityManagerService.java409
-rw-r--r--com/android/server/am/ActivityRecord.java106
-rw-r--r--com/android/server/am/ActivityStack.java125
-rw-r--r--com/android/server/am/ActivityStackSupervisor.java49
-rw-r--r--com/android/server/am/ActivityStarter.java48
-rw-r--r--com/android/server/am/AppBindRecord.java17
-rw-r--r--com/android/server/am/AppTaskImpl.java2
-rw-r--r--com/android/server/am/AppWarnings.java498
-rw-r--r--com/android/server/am/ClientLifecycleManager.java120
-rw-r--r--com/android/server/am/CompatModePackages.java22
-rw-r--r--com/android/server/am/ConnectionRecord.java69
-rw-r--r--com/android/server/am/IntentBindRecord.java32
-rw-r--r--com/android/server/am/KeyguardController.java11
-rw-r--r--com/android/server/am/LaunchingActivityPositioner.java4
-rw-r--r--com/android/server/am/LaunchingBoundsController.java8
-rw-r--r--com/android/server/am/LaunchingTaskPositioner.java19
-rw-r--r--com/android/server/am/LockTaskController.java47
-rw-r--r--com/android/server/am/ProcessList.java10
-rw-r--r--com/android/server/am/ProcessRecord.java15
-rw-r--r--com/android/server/am/ProviderMap.java37
-rw-r--r--com/android/server/am/RecentTasks.java65
-rw-r--r--com/android/server/am/ServiceRecord.java143
-rw-r--r--com/android/server/am/TaskRecord.java238
-rw-r--r--com/android/server/am/UnsupportedCompileSdkDialog.java83
-rw-r--r--com/android/server/am/UnsupportedDisplaySizeDialog.java12
-rw-r--r--com/android/server/am/UriPermissionOwner.java23
-rw-r--r--com/android/server/am/UserController.java38
-rw-r--r--com/android/server/autofill/RemoteFillService.java14
-rw-r--r--com/android/server/autofill/Session.java38
-rw-r--r--com/android/server/autofill/ViewState.java11
-rw-r--r--com/android/server/backup/RefactoredBackupManagerService.java57
-rw-r--r--com/android/server/backup/TransportManager.java80
-rw-r--r--com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java87
-rw-r--r--com/android/server/backup/internal/BackupHandler.java33
-rw-r--r--com/android/server/backup/internal/OnTaskFinishedListener.java34
-rw-r--r--com/android/server/backup/internal/PerformBackupTask.java66
-rw-r--r--com/android/server/backup/params/BackupParams.java16
-rw-r--r--com/android/server/backup/transport/TransportClient.java487
-rw-r--r--com/android/server/backup/transport/TransportClientManager.java83
-rw-r--r--com/android/server/backup/transport/TransportClientTest.java240
-rw-r--r--com/android/server/backup/transport/TransportConnectionListener.java37
-rw-r--r--com/android/server/backup/transport/TransportNotAvailableException.java32
-rw-r--r--com/android/server/backup/transport/TransportUtils.java62
-rw-r--r--com/android/server/broadcastradio/Tuner.java31
-rw-r--r--com/android/server/broadcastradio/TunerCallback.java8
-rw-r--r--com/android/server/connectivity/NetdEventListenerService.java8
-rw-r--r--com/android/server/devicepolicy/DevicePolicyManagerService.java4
-rw-r--r--com/android/server/devicepolicy/NetworkLoggingHandler.java6
-rw-r--r--com/android/server/display/BrightnessIdleJob.java81
-rw-r--r--com/android/server/display/BrightnessTracker.java71
-rw-r--r--com/android/server/display/DisplayManagerService.java10
-rw-r--r--com/android/server/display/DisplayPowerController.java2
-rw-r--r--com/android/server/input/InputManagerService.java2
-rw-r--r--com/android/server/job/JobSchedulerInternal.java12
-rw-r--r--com/android/server/job/JobSchedulerService.java264
-rw-r--r--com/android/server/job/JobServiceContext.java13
-rw-r--r--com/android/server/job/JobStore.java94
-rw-r--r--com/android/server/job/controllers/AppIdleController.java1
-rw-r--r--com/android/server/job/controllers/BackgroundJobsController.java304
-rw-r--r--com/android/server/job/controllers/DeviceIdleJobsController.java125
-rw-r--r--com/android/server/job/controllers/JobStatus.java96
-rw-r--r--com/android/server/location/ContextHubClientBroker.java226
-rw-r--r--com/android/server/location/ContextHubClientManager.java237
-rw-r--r--com/android/server/location/ContextHubService.java656
-rw-r--r--com/android/server/location/ContextHubServiceTransaction.java161
-rw-r--r--com/android/server/location/ContextHubServiceUtil.java211
-rw-r--r--com/android/server/location/ContextHubTransactionManager.java351
-rw-r--r--com/android/server/locksettings/LockSettingsService.java283
-rw-r--r--com/android/server/locksettings/SyntheticPasswordManager.java9
-rw-r--r--com/android/server/media/AudioPlayerStateMonitor.java157
-rw-r--r--com/android/server/media/MediaRouterService.java73
-rw-r--r--com/android/server/media/MediaSessionService.java29
-rw-r--r--com/android/server/net/NetworkPolicyLogger.java514
-rw-r--r--com/android/server/net/NetworkPolicyManagerService.java139
-rw-r--r--com/android/server/notification/NotificationManagerService.java117
-rw-r--r--com/android/server/notification/RankingConfig.java2
-rw-r--r--com/android/server/notification/RankingHelper.java7
-rw-r--r--com/android/server/pm/BackgroundDexOptService.java16
-rw-r--r--com/android/server/pm/Installer.java10
-rw-r--r--com/android/server/pm/PackageDexOptimizer.java8
-rw-r--r--com/android/server/pm/PackageManagerService.java649
-rw-r--r--com/android/server/pm/PackageManagerServiceUtils.java31
-rw-r--r--com/android/server/pm/PackageManagerShellCommand.java16
-rw-r--r--com/android/server/pm/Settings.java5
-rw-r--r--com/android/server/pm/UserManagerService.java13
-rw-r--r--com/android/server/pm/dex/DexLogger.java123
-rw-r--r--com/android/server/pm/dex/DexManager.java23
-rw-r--r--com/android/server/policy/BarController.java2
-rw-r--r--com/android/server/policy/GlobalActions.java10
-rw-r--r--com/android/server/policy/LegacyGlobalActions.java4
-rw-r--r--com/android/server/policy/PhoneWindowManager.java127
-rw-r--r--com/android/server/policy/PolicyControl.java5
-rw-r--r--com/android/server/policy/SplashScreenSurface.java3
-rw-r--r--com/android/server/policy/StatusBarController.java2
-rw-r--r--com/android/server/policy/SystemGesturesPointerEventListener.java2
-rw-r--r--com/android/server/policy/WindowManagerPolicy.java (renamed from android/view/WindowManagerPolicy.java)166
-rw-r--r--com/android/server/policy/keyguard/KeyguardServiceDelegate.java6
-rw-r--r--com/android/server/power/BatterySaverPolicy.java157
-rw-r--r--com/android/server/power/Notifier.java2
-rw-r--r--com/android/server/power/PowerManagerService.java17
-rw-r--r--com/android/server/power/ShutdownThread.java57
-rw-r--r--com/android/server/power/batterysaver/BatterySaverController.java99
-rw-r--r--com/android/server/power/batterysaver/CpuFrequencies.java101
-rw-r--r--com/android/server/power/batterysaver/FileUpdater.java365
-rw-r--r--com/android/server/stats/StatsCompanionService.java52
-rw-r--r--com/android/server/statusbar/StatusBarManagerInternal.java7
-rw-r--r--com/android/server/statusbar/StatusBarManagerService.java9
-rw-r--r--com/android/server/usage/AppIdleHistory.java42
-rw-r--r--com/android/server/usage/AppStandbyController.java78
-rw-r--r--com/android/server/usage/UsageStatsService.java7
-rw-r--r--com/android/server/usage/UserUsageStatsService.java2
-rw-r--r--com/android/server/usb/UsbAlsaManager.java1
-rw-r--r--com/android/server/usb/UsbHostManager.java245
-rw-r--r--com/android/server/usb/UsbService.java13
-rw-r--r--com/android/server/usb/UsbUserSettingsManager.java86
-rw-r--r--com/android/server/usb/descriptors/Usb10ACHeader.java2
-rw-r--r--com/android/server/usb/descriptors/Usb10ACInputTerminal.java2
-rw-r--r--com/android/server/usb/descriptors/Usb10ACMixerUnit.java2
-rw-r--r--com/android/server/usb/descriptors/Usb10ACOutputTerminal.java2
-rw-r--r--com/android/server/usb/descriptors/Usb10ASFormatI.java2
-rw-r--r--com/android/server/usb/descriptors/Usb10ASFormatII.java2
-rw-r--r--com/android/server/usb/descriptors/Usb10ASGeneral.java2
-rw-r--r--com/android/server/usb/descriptors/Usb20ACHeader.java2
-rw-r--r--com/android/server/usb/descriptors/Usb20ACInputTerminal.java2
-rw-r--r--com/android/server/usb/descriptors/Usb20ACMixerUnit.java2
-rw-r--r--com/android/server/usb/descriptors/Usb20ACOutputTerminal.java2
-rw-r--r--com/android/server/usb/descriptors/Usb20ASFormatI.java2
-rw-r--r--com/android/server/usb/descriptors/Usb20ASFormatII.java2
-rw-r--r--com/android/server/usb/descriptors/Usb20ASFormatIII.java2
-rw-r--r--com/android/server/usb/descriptors/Usb20ASGeneral.java2
-rw-r--r--com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java2
-rw-r--r--com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java2
-rw-r--r--com/android/server/usb/descriptors/UsbACEndpoint.java12
-rw-r--r--com/android/server/usb/descriptors/UsbACFeatureUnit.java2
-rw-r--r--com/android/server/usb/descriptors/UsbACHeaderInterface.java2
-rw-r--r--com/android/server/usb/descriptors/UsbACInterface.java16
-rw-r--r--com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java2
-rw-r--r--com/android/server/usb/descriptors/UsbACMidiEndpoint.java2
-rw-r--r--com/android/server/usb/descriptors/UsbACMixerUnit.java2
-rw-r--r--com/android/server/usb/descriptors/UsbACSelectorUnit.java2
-rw-r--r--com/android/server/usb/descriptors/UsbACTerminal.java2
-rw-r--r--com/android/server/usb/descriptors/UsbASFormat.java6
-rw-r--r--com/android/server/usb/descriptors/UsbConfigDescriptor.java52
-rw-r--r--com/android/server/usb/descriptors/UsbDescriptor.java56
-rw-r--r--com/android/server/usb/descriptors/UsbDescriptorParser.java112
-rw-r--r--com/android/server/usb/descriptors/UsbDeviceDescriptor.java66
-rw-r--r--com/android/server/usb/descriptors/UsbEndpointDescriptor.java56
-rw-r--r--com/android/server/usb/descriptors/UsbInterfaceDescriptor.java54
-rw-r--r--com/android/server/usb/descriptors/UsbMSMidiHeader.java2
-rw-r--r--com/android/server/usb/descriptors/UsbMSMidiInputJack.java2
-rw-r--r--com/android/server/usb/descriptors/UsbMSMidiOutputJack.java2
-rw-r--r--com/android/server/usb/descriptors/report/HTMLReportCanvas.java6
-rw-r--r--com/android/server/usb/descriptors/report/ReportCanvas.java15
-rw-r--r--com/android/server/usb/descriptors/report/TextReportCanvas.java6
-rw-r--r--com/android/server/usb/descriptors/report/UsbStrings.java14
-rw-r--r--com/android/server/utils/AppInstallerUtil.java71
-rw-r--r--com/android/server/voiceinteraction/VoiceInteractionManagerService.java3
-rw-r--r--com/android/server/vr/Vr2dDisplay.java3
-rw-r--r--com/android/server/vr/VrManagerService.java2
-rw-r--r--com/android/server/wifi/HalDeviceManager.java96
-rw-r--r--com/android/server/wifi/SoftApManager.java69
-rw-r--r--com/android/server/wifi/WakeupController.java74
-rw-r--r--com/android/server/wifi/WifiInjector.java7
-rw-r--r--com/android/server/wifi/WifiNative.java30
-rw-r--r--com/android/server/wifi/WifiServiceImpl.java166
-rw-r--r--com/android/server/wifi/WifiStateMachine.java38
-rw-r--r--com/android/server/wifi/WificondControl.java120
-rw-r--r--com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java11
-rw-r--r--com/android/server/wifi/aware/WifiAwareNativeApi.java27
-rw-r--r--com/android/server/wifi/aware/WifiAwareNativeCallback.java8
-rw-r--r--com/android/server/wifi/aware/WifiAwareNativeManager.java3
-rw-r--r--com/android/server/wifi/aware/WifiAwareStateManager.java18
-rw-r--r--com/android/server/wifi/hotspot2/OsuNetworkConnection.java270
-rw-r--r--com/android/server/wifi/hotspot2/PasspointManager.java32
-rw-r--r--com/android/server/wifi/hotspot2/PasspointObjectFactory.java22
-rw-r--r--com/android/server/wifi/hotspot2/PasspointProvisioner.java309
-rw-r--r--com/android/server/wifi/p2p/WifiP2pServiceImpl.java3
-rw-r--r--com/android/server/wifi/rtt/RttNative.java2
-rw-r--r--com/android/server/wifi/rtt/RttService.java3
-rw-r--r--com/android/server/wifi/rtt/RttServiceImpl.java103
-rw-r--r--com/android/server/wm/AccessibilityController.java27
-rw-r--r--com/android/server/wm/AppTransition.java2
-rw-r--r--com/android/server/wm/AppWindowAnimator.java18
-rw-r--r--com/android/server/wm/AppWindowContainerController.java27
-rw-r--r--com/android/server/wm/AppWindowToken.java39
-rw-r--r--com/android/server/wm/BlackFrame.java29
-rw-r--r--com/android/server/wm/BoundsAnimationController.java1
-rw-r--r--com/android/server/wm/CircularDisplayMask.java7
-rw-r--r--com/android/server/wm/ConfigurationContainer.java132
-rw-r--r--com/android/server/wm/DimLayer.java380
-rw-r--r--com/android/server/wm/DimLayerController.java403
-rw-r--r--com/android/server/wm/Dimmer.java195
-rw-r--r--com/android/server/wm/DisplayContent.java708
-rw-r--r--com/android/server/wm/DisplayFrames.java (renamed from android/view/DisplayFrames.java)5
-rw-r--r--com/android/server/wm/DockedStackDividerController.java79
-rw-r--r--com/android/server/wm/DragDropController.java3
-rw-r--r--com/android/server/wm/DragState.java10
-rw-r--r--com/android/server/wm/EmulatorDisplayOverlay.java6
-rw-r--r--com/android/server/wm/InputMonitor.java6
-rw-r--r--com/android/server/wm/KeyguardDisableHandler.java3
-rw-r--r--com/android/server/wm/PinnedStackWindowController.java2
-rw-r--r--com/android/server/wm/PointerEventDispatcher.java2
-rw-r--r--com/android/server/wm/RootWindowContainer.java15
-rw-r--r--com/android/server/wm/ScreenRotationAnimation.java32
-rw-r--r--com/android/server/wm/SnapshotStartingData.java4
-rw-r--r--com/android/server/wm/SplashScreenStartingData.java3
-rw-r--r--com/android/server/wm/StackWindowController.java24
-rw-r--r--com/android/server/wm/StartingData.java2
-rw-r--r--com/android/server/wm/StrictModeFlash.java5
-rw-r--r--com/android/server/wm/SurfaceBuilderFactory.java25
-rw-r--r--com/android/server/wm/SurfaceControlWithBackground.java15
-rw-r--r--com/android/server/wm/Task.java188
-rw-r--r--com/android/server/wm/TaskPositioner.java129
-rw-r--r--com/android/server/wm/TaskSnapshotController.java32
-rw-r--r--com/android/server/wm/TaskSnapshotSurface.java4
-rw-r--r--com/android/server/wm/TaskStack.java309
-rw-r--r--com/android/server/wm/TaskTapPointerEventListener.java2
-rw-r--r--com/android/server/wm/TaskWindowContainerController.java16
-rw-r--r--com/android/server/wm/WallpaperController.java2
-rw-r--r--com/android/server/wm/Watermark.java6
-rw-r--r--com/android/server/wm/WindowAnimator.java15
-rw-r--r--com/android/server/wm/WindowContainer.java184
-rw-r--r--com/android/server/wm/WindowLayersController.java273
-rw-r--r--com/android/server/wm/WindowManagerInternal.java (renamed from android/view/WindowManagerInternal.java)15
-rw-r--r--com/android/server/wm/WindowManagerService.java95
-rw-r--r--com/android/server/wm/WindowManagerShellCommand.java10
-rw-r--r--com/android/server/wm/WindowState.java224
-rw-r--r--com/android/server/wm/WindowStateAnimator.java141
-rw-r--r--com/android/server/wm/WindowSurfaceController.java24
-rw-r--r--com/android/server/wm/WindowSurfacePlacer.java12
-rw-r--r--com/android/server/wm/WindowTracing.java16
-rw-r--r--com/android/settingslib/Utils.java3
-rw-r--r--com/android/settingslib/drawer/CategoryManager.java41
-rw-r--r--com/android/settingslib/drawer/DashboardCategory.java91
-rw-r--r--com/android/settingslib/drawer/TileUtils.java10
-rw-r--r--com/android/settingslib/drawer/UserAdapter.java3
-rw-r--r--com/android/setupwizardlib/GlifPatternDrawable.java23
-rw-r--r--com/android/systemui/OverviewProxyService.java15
-rw-r--r--com/android/systemui/SystemUIFactory.java7
-rw-r--r--com/android/systemui/doze/DozeHost.java1
-rw-r--r--com/android/systemui/keyguard/KeyguardSliceProvider.java2
-rw-r--r--com/android/systemui/keyguard/KeyguardViewMediator.java27
-rw-r--r--com/android/systemui/pip/phone/PipTouchHandler.java8
-rw-r--r--com/android/systemui/pip/tv/PipManager.java16
-rw-r--r--com/android/systemui/recents/RecentsImpl.java13
-rw-r--r--com/android/systemui/recents/misc/SystemServicesProxy.java38
-rw-r--r--com/android/systemui/recents/views/RecentsView.java34
-rw-r--r--com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java4
-rw-r--r--com/android/systemui/recents/views/TaskStackView.java8
-rw-r--r--com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java17
-rw-r--r--com/android/systemui/shared/recents/model/RecentsTaskLoader.java3
-rw-r--r--com/android/systemui/shared/recents/utilities/AppTrace.java73
-rw-r--r--com/android/systemui/shared/system/ActivityManagerWrapper.java43
-rw-r--r--com/android/systemui/shared/system/ActivityOptionsCompat.java41
-rw-r--r--com/android/systemui/shared/system/BackgroundExecutor.java8
-rw-r--r--com/android/systemui/shared/system/ChoreographerCompat.java33
-rw-r--r--com/android/systemui/shared/system/GraphicBufferCompat.java64
-rw-r--r--com/android/systemui/shared/system/WindowManagerWrapper.java27
-rw-r--r--com/android/systemui/shortcut/ShortcutKeyDispatcher.java18
-rw-r--r--com/android/systemui/statusbar/ActivatableNotificationView.java38
-rw-r--r--com/android/systemui/statusbar/CommandQueue.java17
-rw-r--r--com/android/systemui/statusbar/ExpandableNotificationRow.java86
-rw-r--r--com/android/systemui/statusbar/ExpandableOutlineView.java266
-rw-r--r--com/android/systemui/statusbar/ExpandableView.java4
-rw-r--r--com/android/systemui/statusbar/NotificationBackgroundView.java73
-rw-r--r--com/android/systemui/statusbar/NotificationContentView.java86
-rw-r--r--com/android/systemui/statusbar/NotificationGutsManager.java11
-rw-r--r--com/android/systemui/statusbar/NotificationMenuRow.java6
-rw-r--r--com/android/systemui/statusbar/NotificationShelf.java22
-rw-r--r--com/android/systemui/statusbar/RemoteInputController.java35
-rw-r--r--com/android/systemui/statusbar/ScrimView.java50
-rw-r--r--com/android/systemui/statusbar/car/CarNavigationBarController.java2
-rw-r--r--com/android/systemui/statusbar/notification/AnimatableProperty.java77
-rw-r--r--com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java2
-rw-r--r--com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java15
-rw-r--r--com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java7
-rw-r--r--com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java5
-rw-r--r--com/android/systemui/statusbar/notification/NotificationViewWrapper.java4
-rw-r--r--com/android/systemui/statusbar/notification/PropertyAnimator.java19
-rw-r--r--com/android/systemui/statusbar/notification/TransformState.java16
-rw-r--r--com/android/systemui/statusbar/phone/DozeParameters.java16
-rw-r--r--com/android/systemui/statusbar/phone/DozeScrimController.java317
-rw-r--r--com/android/systemui/statusbar/phone/FingerprintUnlockController.java10
-rw-r--r--com/android/systemui/statusbar/phone/KeyguardStatusBarView.java2
-rw-r--r--com/android/systemui/statusbar/phone/LockIcon.java2
-rw-r--r--com/android/systemui/statusbar/phone/NavigationBarTransitions.java10
-rw-r--r--com/android/systemui/statusbar/phone/NotificationIconContainer.java2
-rw-r--r--com/android/systemui/statusbar/phone/NotificationPanelView.java25
-rw-r--r--com/android/systemui/statusbar/phone/ScrimController.java660
-rw-r--r--com/android/systemui/statusbar/phone/ScrimState.java208
-rw-r--r--com/android/systemui/statusbar/phone/StatusBar.java170
-rw-r--r--com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java90
-rw-r--r--com/android/systemui/statusbar/policy/BrightnessMirrorController.java37
-rw-r--r--com/android/systemui/statusbar/policy/RemoteInputView.java33
-rw-r--r--com/android/systemui/statusbar/policy/UserInfoControllerImpl.java4
-rw-r--r--com/android/systemui/statusbar/policy/UserSwitcherController.java7
-rw-r--r--com/android/systemui/statusbar/stack/AnimationProperties.java7
-rw-r--r--com/android/systemui/statusbar/stack/NotificationChildrenContainer.java13
-rw-r--r--com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java130
-rw-r--r--com/android/systemui/statusbar/stack/ScrollContainer.java48
-rw-r--r--com/android/systemui/statusbar/stack/ViewState.java15
-rw-r--r--com/android/systemui/usb/UsbPermissionActivity.java4
-rw-r--r--com/android/systemui/volume/VolumeDialogImpl.java4
-rw-r--r--com/android/uiautomator/testrunner/UiAutomatorTestCase.java106
-rw-r--r--java/lang/Math.java11
-rw-r--r--java/lang/NoClassDefFoundError.java1
-rw-r--r--java/lang/Package.java8
-rw-r--r--java/lang/ProcessBuilder.java4
-rw-r--r--java/lang/reflect/AccessibleObject.java104
-rw-r--r--java/lang/reflect/AnnotatedElement.java8
-rw-r--r--java/lang/reflect/Array.java204
-rw-r--r--java/text/DateFormatSymbols.java8
-rw-r--r--java/text/SimpleDateFormat.java25
-rw-r--r--javax/obex/ServerOperation.java16
708 files changed, 30667 insertions, 14959 deletions
diff --git a/android/accessibilityservice/AccessibilityService.java b/android/accessibilityservice/AccessibilityService.java
index a558d685..8824643d 100644
--- a/android/accessibilityservice/AccessibilityService.java
+++ b/android/accessibilityservice/AccessibilityService.java
@@ -358,6 +358,11 @@ public abstract class AccessibilityService extends Service {
*/
public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7;
+ /**
+ * Action to lock the screen
+ */
+ public static final int GLOBAL_ACTION_LOCK_SCREEN = 8;
+
private static final String LOG_TAG = "AccessibilityService";
/**
diff --git a/android/accessibilityservice/AccessibilityServiceInfo.java b/android/accessibilityservice/AccessibilityServiceInfo.java
index 06a9b067..e0d60cd0 100644
--- a/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -16,6 +16,8 @@
package android.accessibilityservice;
+import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -47,8 +49,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
-
/**
* This class describes an {@link AccessibilityService}. The system notifies an
* {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s
@@ -554,7 +554,7 @@ public class AccessibilityServiceInfo implements Parcelable {
}
/**
- * Updates the properties that an AccessibilitySerivice can change dynamically.
+ * Updates the properties that an AccessibilityService can change dynamically.
*
* @param other The info from which to update the properties.
*
diff --git a/android/app/Activity.java b/android/app/Activity.java
index 99f3dee7..03a3631b 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -4379,7 +4379,7 @@ public class Activity extends ContextThemeWrapper
throw new IllegalArgumentException("requestCode should be >= 0");
}
if (mHasCurrentPermissionsRequest) {
- Log.w(TAG, "Can reqeust only one set of permissions at a time");
+ Log.w(TAG, "Can request only one set of permissions at a time");
// Dispatch the callback with empty arrays which means a cancellation.
onRequestPermissionsResult(requestCode, new String[0], new int[0]);
return;
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index 064e9782..02b7f8c5 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -519,11 +519,15 @@ public class ActivityManager {
* process that contains activities. */
public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 16;
+ /** @hide Process is being cached for later use and has an activity that corresponds
+ * to an existing recent task. */
+ public static final int PROCESS_STATE_CACHED_RECENT = 17;
+
/** @hide Process is being cached for later use and is empty. */
- public static final int PROCESS_STATE_CACHED_EMPTY = 17;
+ public static final int PROCESS_STATE_CACHED_EMPTY = 18;
/** @hide Process does not exist. */
- public static final int PROCESS_STATE_NONEXISTENT = 18;
+ public static final int PROCESS_STATE_NONEXISTENT = 19;
// NOTE: If PROCESS_STATEs are added or changed, then new fields must be added
// to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must
diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java
index a46b3c72..d7efa91f 100644
--- a/android/app/ActivityManagerInternal.java
+++ b/android/app/ActivityManagerInternal.java
@@ -283,4 +283,20 @@ public abstract class ActivityManagerInternal {
* @param token The IApplicationToken for the activity
*/
public abstract void setFocusedActivity(IBinder token);
+
+ /**
+ * Set a uid that is allowed to bypass stopped app switches, launching an app
+ * whenever it wants.
+ *
+ * @param type Type of the caller -- unique string the caller supplies to identify itself
+ * and disambiguate with other calles.
+ * @param uid The uid of the app to be allowed, or -1 to clear the uid for this type.
+ * @param userId The user it is allowed for.
+ */
+ public abstract void setAllowAppSwitches(@NonNull String type, int uid, int userId);
+
+ /**
+ * @return true if runtime was restarted, false if it's normal boot
+ */
+ public abstract boolean isRuntimeRestarted();
}
diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java
index 21e454f1..ffd012d9 100644
--- a/android/app/ActivityThread.java
+++ b/android/app/ActivityThread.java
@@ -23,6 +23,8 @@ import android.annotation.Nullable;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.BackupAgent;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.ClientTransaction;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
@@ -174,7 +176,7 @@ final class RemoteServiceException extends AndroidRuntimeException {
*
* {@hide}
*/
-public final class ActivityThread {
+public final class ActivityThread extends ClientTransactionHandler {
/** @hide */
public static final String TAG = "ActivityThread";
private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
@@ -401,8 +403,8 @@ public final class ActivityThread {
throw new IllegalStateException(
"Received config update for non-existing activity");
}
- activity.mMainThread.handleActivityConfigurationChanged(
- new ActivityConfigChangeData(token, overrideConfig), newDisplayId);
+ activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig,
+ newDisplayId);
};
}
@@ -469,16 +471,6 @@ public final class ActivityThread {
}
}
- static final class NewIntentData {
- List<ReferrerIntent> intents;
- IBinder token;
- boolean andPause;
- public String toString() {
- return "NewIntentData{intents=" + intents + " token=" + token
- + " andPause=" + andPause +"}";
- }
- }
-
static final class ReceiverData extends BroadcastReceiver.PendingResult {
public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
boolean ordered, boolean sticky, IBinder token, int sendingUser) {
@@ -644,14 +636,6 @@ public final class ActivityThread {
String[] args;
}
- static final class ResultData {
- IBinder token;
- List<ResultInfo> results;
- public String toString() {
- return "ResultData{token=" + token + " results" + results + "}";
- }
- }
-
static final class ContextCleanupInfo {
ContextImpl context;
String what;
@@ -679,15 +663,6 @@ public final class ActivityThread {
int flags;
}
- static final class ActivityConfigChangeData {
- final IBinder activityToken;
- final Configuration overrideConfig;
- public ActivityConfigChangeData(IBinder token, Configuration config) {
- activityToken = token;
- overrideConfig = config;
- }
- }
-
private class ApplicationThread extends IApplicationThread.Stub {
private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s";
@@ -702,93 +677,10 @@ public final class ActivityThread {
}
}
- public final void schedulePauseActivity(IBinder token, boolean finished,
- boolean userLeaving, int configChanges, boolean dontReport) {
- int seq = getLifecycleSeq();
- if (DEBUG_ORDER) Slog.d(TAG, "pauseActivity " + ActivityThread.this
- + " operation received seq: " + seq);
- sendMessage(
- finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
- token,
- (userLeaving ? USER_LEAVING : 0) | (dontReport ? DONT_REPORT : 0),
- configChanges,
- seq);
- }
-
- public final void scheduleStopActivity(IBinder token, boolean showWindow,
- int configChanges) {
- int seq = getLifecycleSeq();
- if (DEBUG_ORDER) Slog.d(TAG, "stopActivity " + ActivityThread.this
- + " operation received seq: " + seq);
- sendMessage(
- showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
- token, 0, configChanges, seq);
- }
-
- public final void scheduleWindowVisibility(IBinder token, boolean showWindow) {
- sendMessage(
- showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW,
- token);
- }
-
public final void scheduleSleeping(IBinder token, boolean sleeping) {
sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
}
- public final void scheduleResumeActivity(IBinder token, int processState,
- boolean isForward, Bundle resumeArgs) {
- int seq = getLifecycleSeq();
- if (DEBUG_ORDER) Slog.d(TAG, "resumeActivity " + ActivityThread.this
- + " operation received seq: " + seq);
- updateProcessState(processState, false);
- sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0, 0, seq);
- }
-
- public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
- ResultData res = new ResultData();
- res.token = token;
- res.results = results;
- sendMessage(H.SEND_RESULT, res);
- }
-
- // we use token to identify this activity without having to send the
- // activity itself back to the activity manager. (matters more with ipc)
- @Override
- public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
- CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
- int procState, Bundle state, PersistableBundle persistentState,
- List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
-
- updateProcessState(procState, false);
-
- ActivityClientRecord r = new ActivityClientRecord();
-
- r.token = token;
- r.ident = ident;
- r.intent = intent;
- r.referrer = referrer;
- r.voiceInteractor = voiceInteractor;
- r.activityInfo = info;
- r.compatInfo = compatInfo;
- r.state = state;
- r.persistentState = persistentState;
-
- r.pendingResults = pendingResults;
- r.pendingIntents = pendingNewIntents;
-
- r.startsNotResumed = notResumed;
- r.isForward = isForward;
-
- r.profilerInfo = profilerInfo;
-
- r.overrideConfig = overrideConfig;
- updatePendingConfiguration(curConfig);
-
- sendMessage(H.LAUNCH_ACTIVITY, r);
- }
-
@Override
public final void scheduleRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
@@ -798,22 +690,6 @@ public final class ActivityThread {
configChanges, notResumed, config, overrideConfig, true, preserveWindow);
}
- public final void scheduleNewIntent(
- List<ReferrerIntent> intents, IBinder token, boolean andPause) {
- NewIntentData data = new NewIntentData();
- data.intents = intents;
- data.token = token;
- data.andPause = andPause;
-
- sendMessage(H.NEW_INTENT, data);
- }
-
- public final void scheduleDestroyActivity(IBinder token, boolean finishing,
- int configChanges) {
- sendMessage(H.DESTROY_ACTIVITY, token, finishing ? 1 : 0,
- configChanges);
- }
-
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
boolean sync, int sendingUser, int processState) {
@@ -949,11 +825,6 @@ public final class ActivityThread {
sendMessage(H.SUICIDE, null);
}
- public void scheduleConfigurationChanged(Configuration config) {
- updatePendingConfiguration(config);
- sendMessage(H.CONFIGURATION_CHANGED, config);
- }
-
public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
sendMessage(H.APPLICATION_INFO_CHANGED, ai);
}
@@ -1016,20 +887,6 @@ public final class ActivityThread {
}
@Override
- public void scheduleActivityConfigurationChanged(
- IBinder token, Configuration overrideConfig) {
- sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED,
- new ActivityConfigChangeData(token, overrideConfig));
- }
-
- @Override
- public void scheduleActivityMovedToDisplay(IBinder token, int displayId,
- Configuration overrideConfig) {
- sendMessage(H.ACTIVITY_MOVED_TO_DISPLAY,
- new ActivityConfigChangeData(token, overrideConfig), displayId);
- }
-
- @Override
public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
sendMessage(H.PROFILER_CONTROL, profilerInfo, start ? 1 : 0, profileType);
}
@@ -1427,26 +1284,6 @@ public final class ActivityThread {
}
@Override
- public void scheduleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
- Configuration overrideConfig) throws RemoteException {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = token;
- args.arg2 = overrideConfig;
- args.argi1 = isInMultiWindowMode ? 1 : 0;
- sendMessage(H.MULTI_WINDOW_MODE_CHANGED, args);
- }
-
- @Override
- public void schedulePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
- Configuration overrideConfig) throws RemoteException {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = token;
- args.arg2 = overrideConfig;
- args.argi1 = isInPipMode ? 1 : 0;
- sendMessage(H.PICTURE_IN_PICTURE_MODE_CHANGED, args);
- }
-
- @Override
public void scheduleLocalVoiceInteractionStarted(IBinder token,
IVoiceInteractor voiceInteractor) throws RemoteException {
SomeArgs args = SomeArgs.obtain();
@@ -1459,28 +1296,33 @@ public final class ActivityThread {
public void handleTrustStorageUpdate() {
NetworkSecurityPolicy.getInstance().handleTrustStorageUpdate();
}
+
+ @Override
+ public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+ ActivityThread.this.scheduleTransaction(transaction);
+ }
+ }
+
+ @Override
+ public void updatePendingConfiguration(Configuration config) {
+ mAppThread.updatePendingConfiguration(config);
+ }
+
+ @Override
+ public void updateProcessState(int processState, boolean fromIpc) {
+ mAppThread.updateProcessState(processState, fromIpc);
}
- private int getLifecycleSeq() {
+ @Override
+ public int getLifecycleSeq() {
synchronized (mResourcesManager) {
return mLifecycleSeq++;
}
}
- private class H extends Handler {
- public static final int LAUNCH_ACTIVITY = 100;
- public static final int PAUSE_ACTIVITY = 101;
- public static final int PAUSE_ACTIVITY_FINISHING= 102;
- public static final int STOP_ACTIVITY_SHOW = 103;
- public static final int STOP_ACTIVITY_HIDE = 104;
- public static final int SHOW_WINDOW = 105;
- public static final int HIDE_WINDOW = 106;
- public static final int RESUME_ACTIVITY = 107;
- public static final int SEND_RESULT = 108;
- public static final int DESTROY_ACTIVITY = 109;
+ class H extends Handler {
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
- public static final int NEW_INTENT = 112;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
@@ -1493,7 +1335,6 @@ public final class ActivityThread {
public static final int UNBIND_SERVICE = 122;
public static final int DUMP_SERVICE = 123;
public static final int LOW_MEMORY = 124;
- public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
public static final int RELAUNCH_ACTIVITY = 126;
public static final int PROFILER_CONTROL = 127;
public static final int CREATE_BACKUP_AGENT = 128;
@@ -1518,30 +1359,17 @@ public final class ActivityThread {
public static final int ENTER_ANIMATION_COMPLETE = 149;
public static final int START_BINDER_TRACKING = 150;
public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
- public static final int MULTI_WINDOW_MODE_CHANGED = 152;
- public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
public static final int ATTACH_AGENT = 155;
public static final int APPLICATION_INFO_CHANGED = 156;
- public static final int ACTIVITY_MOVED_TO_DISPLAY = 157;
public static final int RUN_ISOLATED_ENTRY_POINT = 158;
+ public static final int EXECUTE_TRANSACTION = 159;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
- case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
- case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
- case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
- case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
- case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE";
- case SHOW_WINDOW: return "SHOW_WINDOW";
- case HIDE_WINDOW: return "HIDE_WINDOW";
- case RESUME_ACTIVITY: return "RESUME_ACTIVITY";
- case SEND_RESULT: return "SEND_RESULT";
- case DESTROY_ACTIVITY: return "DESTROY_ACTIVITY";
case BIND_APPLICATION: return "BIND_APPLICATION";
case EXIT_APPLICATION: return "EXIT_APPLICATION";
- case NEW_INTENT: return "NEW_INTENT";
case RECEIVER: return "RECEIVER";
case CREATE_SERVICE: return "CREATE_SERVICE";
case SERVICE_ARGS: return "SERVICE_ARGS";
@@ -1553,8 +1381,6 @@ public final class ActivityThread {
case UNBIND_SERVICE: return "UNBIND_SERVICE";
case DUMP_SERVICE: return "DUMP_SERVICE";
case LOW_MEMORY: return "LOW_MEMORY";
- case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED";
- case ACTIVITY_MOVED_TO_DISPLAY: return "ACTIVITY_MOVED_TO_DISPLAY";
case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
case PROFILER_CONTROL: return "PROFILER_CONTROL";
case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
@@ -1577,12 +1403,11 @@ public final class ActivityThread {
case INSTALL_PROVIDER: return "INSTALL_PROVIDER";
case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS";
case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
- case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED";
- case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED";
case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
case ATTACH_AGENT: return "ATTACH_AGENT";
case APPLICATION_INFO_CHANGED: return "APPLICATION_INFO_CHANGED";
case RUN_ISOLATED_ENTRY_POINT: return "RUN_ISOLATED_ENTRY_POINT";
+ case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
}
}
return Integer.toString(code);
@@ -1590,76 +1415,12 @@ public final class ActivityThread {
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
- case LAUNCH_ACTIVITY: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
- final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
-
- r.packageInfo = getPackageInfoNoCheck(
- r.activityInfo.applicationInfo, r.compatInfo);
- handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
- case PAUSE_ACTIVITY: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
- SomeArgs args = (SomeArgs) msg.obj;
- handlePauseActivity((IBinder) args.arg1, false,
- (args.argi1 & USER_LEAVING) != 0, args.argi2,
- (args.argi1 & DONT_REPORT) != 0, args.argi3);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
- case PAUSE_ACTIVITY_FINISHING: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
- SomeArgs args = (SomeArgs) msg.obj;
- handlePauseActivity((IBinder) args.arg1, true, (args.argi1 & USER_LEAVING) != 0,
- args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
- case STOP_ACTIVITY_SHOW: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
- SomeArgs args = (SomeArgs) msg.obj;
- handleStopActivity((IBinder) args.arg1, true, args.argi2, args.argi3);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
- case STOP_ACTIVITY_HIDE: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
- SomeArgs args = (SomeArgs) msg.obj;
- handleStopActivity((IBinder) args.arg1, false, args.argi2, args.argi3);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
- case SHOW_WINDOW:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow");
- handleWindowVisibility((IBinder)msg.obj, true);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case HIDE_WINDOW:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityHideWindow");
- handleWindowVisibility((IBinder)msg.obj, false);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case RESUME_ACTIVITY:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
- SomeArgs args = (SomeArgs) msg.obj;
- handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
- args.argi3, "RESUME_ACTIVITY");
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case SEND_RESULT:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
- handleSendResult((ResultData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case DESTROY_ACTIVITY:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
- handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
- msg.arg2, false);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
@@ -1672,11 +1433,6 @@ public final class ActivityThread {
}
Looper.myLooper().quit();
break;
- case NEW_INTENT:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
- handleNewIntent((NewIntentData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
case RECEIVER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
handleReceiver((ReceiverData)msg.obj);
@@ -1708,15 +1464,7 @@ public final class ActivityThread {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CONFIGURATION_CHANGED:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
- mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
- mUpdatingSystemConfig = true;
- try {
- handleConfigurationChanged((Configuration) msg.obj, null);
- } finally {
- mUpdatingSystemConfig = false;
- }
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ handleConfigurationChanged((Configuration) msg.obj);
break;
case CLEAN_UP_CONTEXT:
ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
@@ -1733,18 +1481,6 @@ public final class ActivityThread {
handleLowMemory();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
- case ACTIVITY_CONFIGURATION_CHANGED:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
- handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj,
- INVALID_DISPLAY);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case ACTIVITY_MOVED_TO_DISPLAY:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
- handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj,
- msg.arg1 /* displayId */);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
case PROFILER_CONTROL:
handleProfilerControl(msg.arg1 != 0, (ProfilerInfo)msg.obj, msg.arg2);
break;
@@ -1828,16 +1564,6 @@ public final class ActivityThread {
case STOP_BINDER_TRACKING_AND_DUMP:
handleStopBinderTrackingAndDump((ParcelFileDescriptor) msg.obj);
break;
- case MULTI_WINDOW_MODE_CHANGED:
- handleMultiWindowModeChanged((IBinder) ((SomeArgs) msg.obj).arg1,
- ((SomeArgs) msg.obj).argi1 == 1,
- (Configuration) ((SomeArgs) msg.obj).arg2);
- break;
- case PICTURE_IN_PICTURE_MODE_CHANGED:
- handlePictureInPictureModeChanged((IBinder) ((SomeArgs) msg.obj).arg1,
- ((SomeArgs) msg.obj).argi1 == 1,
- (Configuration) ((SomeArgs) msg.obj).arg2);
- break;
case LOCAL_VOICE_INTERACTION_STARTED:
handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
(IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
@@ -1857,6 +1583,9 @@ public final class ActivityThread {
handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
(String[]) ((SomeArgs) msg.obj).arg2);
break;
+ case EXECUTE_TRANSACTION:
+ ((ClientTransaction) msg.obj).execute(ActivityThread.this);
+ break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
@@ -2601,10 +2330,16 @@ public final class ActivityThread {
+ " req=" + requestCode + " res=" + resultCode + " data=" + data);
ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
list.add(new ResultInfo(id, requestCode, resultCode, data));
- mAppThread.scheduleSendResult(token, list);
+ final ClientTransaction clientTransaction = new ClientTransaction(mAppThread, token);
+ clientTransaction.addCallback(new ActivityResultItem(list));
+ try {
+ mAppThread.scheduleTransaction(clientTransaction);
+ } catch (RemoteException e) {
+ // Local scheduling
+ }
}
- private void sendMessage(int what, Object obj) {
+ void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}
@@ -2844,6 +2579,37 @@ public final class ActivityThread {
return appContext;
}
+ @Override
+ public void handleLaunchActivity(IBinder token, Intent intent, int ident, ActivityInfo info,
+ Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer,
+ IVoiceInteractor voiceInteractor, Bundle state, PersistableBundle persistentState,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+ boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
+ ActivityClientRecord r = new ActivityClientRecord();
+
+ r.token = token;
+ r.ident = ident;
+ r.intent = intent;
+ r.referrer = referrer;
+ r.voiceInteractor = voiceInteractor;
+ r.activityInfo = info;
+ r.compatInfo = compatInfo;
+ r.state = state;
+ r.persistentState = persistentState;
+
+ r.pendingResults = pendingResults;
+ r.pendingIntents = pendingNewIntents;
+
+ r.startsNotResumed = notResumed;
+ r.isForward = isForward;
+
+ r.profilerInfo = profilerInfo;
+
+ r.overrideConfig = overrideConfig;
+ r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
+ handleLaunchActivity(r, null /* customIntent */, "LAUNCH_ACTIVITY");
+ }
+
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
@@ -2974,8 +2740,9 @@ public final class ActivityThread {
}
}
- private void handleNewIntent(NewIntentData data) {
- performNewIntents(data.token, data.intents, data.andPause);
+ @Override
+ public void handleNewIntent(IBinder token, List<ReferrerIntent> intents, boolean andPause) {
+ performNewIntents(token, intents, andPause);
}
public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) {
@@ -3096,7 +2863,8 @@ public final class ActivityThread {
}
}
- private void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
+ @Override
+ public void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
Configuration overrideConfig) {
final ActivityClientRecord r = mActivities.get(token);
if (r != null) {
@@ -3108,7 +2876,8 @@ public final class ActivityThread {
}
}
- private void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
+ @Override
+ public void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
Configuration overrideConfig) {
final ActivityClientRecord r = mActivities.get(token);
if (r != null) {
@@ -3619,8 +3388,9 @@ public final class ActivityThread {
r.mPendingRemoveWindowManager = null;
}
- final void handleResumeActivity(IBinder token,
- boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
+ @Override
+ public void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
+ boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
return;
@@ -3823,7 +3593,8 @@ public final class ActivityThread {
return thumbnail;
}
- private void handlePauseActivity(IBinder token, boolean finished,
+ @Override
+ public void handlePauseActivity(IBinder token, boolean finished,
boolean userLeaving, int configChanges, boolean dontReport, int seq) {
ActivityClientRecord r = mActivities.get(token);
if (DEBUG_ORDER) Slog.d(TAG, "handlePauseActivity " + r + ", seq: " + seq);
@@ -4087,7 +3858,8 @@ public final class ActivityThread {
}
}
- private void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {
+ @Override
+ public void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "stopActivity")) {
return;
@@ -4142,7 +3914,8 @@ public final class ActivityThread {
}
}
- private void handleWindowVisibility(IBinder token, boolean show) {
+ @Override
+ public void handleWindowVisibility(IBinder token, boolean show) {
ActivityClientRecord r = mActivities.get(token);
if (r == null) {
@@ -4288,8 +4061,9 @@ public final class ActivityThread {
}
}
- private void handleSendResult(ResultData res) {
- ActivityClientRecord r = mActivities.get(res.token);
+ @Override
+ public void handleSendResult(IBinder token, List<ResultInfo> results) {
+ ActivityClientRecord r = mActivities.get(token);
if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r);
if (r != null) {
final boolean resumed = !r.paused;
@@ -4323,7 +4097,7 @@ public final class ActivityThread {
}
}
checkAndBlockForNetworkAccess();
- deliverResults(r, res.results);
+ deliverResults(r, results);
if (resumed) {
r.activity.performResume();
r.activity.mTemporaryPause = false;
@@ -4410,8 +4184,9 @@ public final class ActivityThread {
return component == null ? "[Unknown]" : component.toShortString();
}
- private void handleDestroyActivity(IBinder token, boolean finishing,
- int configChanges, boolean getNonConfigInstance) {
+ @Override
+ public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+ boolean getNonConfigInstance) {
ActivityClientRecord r = performDestroyActivity(token, finishing,
configChanges, getNonConfigInstance);
if (r != null) {
@@ -4982,7 +4757,20 @@ public final class ActivityThread {
return config;
}
- final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
+ @Override
+ public void handleConfigurationChanged(Configuration config) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
+ mCurDefaultDisplayDpi = config.densityDpi;
+ mUpdatingSystemConfig = true;
+ try {
+ handleConfigurationChanged(config, null /* compat */);
+ } finally {
+ mUpdatingSystemConfig = false;
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ private void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
int configDiff = 0;
@@ -5113,12 +4901,15 @@ public final class ActivityThread {
/**
* Handle new activity configuration and/or move to a different display.
- * @param data Configuration update data.
+ * @param activityToken Target activity token.
+ * @param overrideConfig Activity override config.
* @param displayId Id of the display where activity was moved to, -1 if there was no move and
* value didn't change.
*/
- void handleActivityConfigurationChanged(ActivityConfigChangeData data, int displayId) {
- ActivityClientRecord r = mActivities.get(data.activityToken);
+ @Override
+ public void handleActivityConfigurationChanged(IBinder activityToken,
+ Configuration overrideConfig, int displayId) {
+ ActivityClientRecord r = mActivities.get(activityToken);
// Check input params.
if (r == null || r.activity == null) {
if (DEBUG_CONFIGURATION) Slog.w(TAG, "Not found target activity to report to: " + r);
@@ -5128,14 +4919,14 @@ public final class ActivityThread {
&& displayId != r.activity.getDisplay().getDisplayId();
// Perform updates.
- r.overrideConfig = data.overrideConfig;
+ r.overrideConfig = overrideConfig;
final ViewRootImpl viewRoot = r.activity.mDecor != null
? r.activity.mDecor.getViewRootImpl() : null;
if (movedToDifferentDisplay) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:"
+ r.activityInfo.name + ", displayId=" + displayId
- + ", config=" + data.overrideConfig);
+ + ", config=" + overrideConfig);
final Configuration reportedConfig = performConfigurationChangedForActivity(r,
mCompatConfiguration, displayId, true /* movedToDifferentDisplay */);
@@ -5144,7 +4935,7 @@ public final class ActivityThread {
}
} else {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
- + r.activityInfo.name + ", config=" + data.overrideConfig);
+ + r.activityInfo.name + ", config=" + overrideConfig);
performConfigurationChangedForActivity(r, mCompatConfiguration);
}
// Notify the ViewRootImpl instance about configuration changes. It may have initiated this
@@ -5380,7 +5171,7 @@ public final class ActivityThread {
}
}
- GraphicsEnvironment.chooseDriver(context);
+ GraphicsEnvironment.getInstance().setup(context);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/android/app/ApplicationLoaders.java b/android/app/ApplicationLoaders.java
index b7c1f4e0..72570442 100644
--- a/android/app/ApplicationLoaders.java
+++ b/android/app/ApplicationLoaders.java
@@ -17,9 +17,12 @@
package android.app;
import android.os.Build;
+import android.os.GraphicsEnvironment;
import android.os.Trace;
import android.util.ArrayMap;
+
import com.android.internal.os.ClassLoaderFactory;
+
import dalvik.system.PathClassLoader;
/** @hide */
@@ -72,8 +75,9 @@ public class ApplicationLoaders {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setupVulkanLayerPath");
- setupVulkanLayerPath(classloader, librarySearchPath);
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setLayerPaths");
+ GraphicsEnvironment.getInstance().setLayerPaths(
+ classloader, librarySearchPath, libraryPermittedPath);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
mLoaders.put(cacheKey, classloader);
@@ -105,8 +109,6 @@ public class ApplicationLoaders {
cacheKey, null /* classLoaderName */);
}
- private static native void setupVulkanLayerPath(ClassLoader classLoader, String librarySearchPath);
-
/**
* Adds a new path the classpath of the given loader.
* @throws IllegalStateException if the provided class loader is not a {@link PathClassLoader}.
diff --git a/android/app/ApplicationPackageManager.java b/android/app/ApplicationPackageManager.java
index 0eafdec6..005b7c38 100644
--- a/android/app/ApplicationPackageManager.java
+++ b/android/app/ApplicationPackageManager.java
@@ -35,7 +35,6 @@ import android.content.pm.FeatureInfo;
import android.content.pm.IOnPermissionsChangeListener;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
@@ -1681,21 +1680,8 @@ public class ApplicationPackageManager extends PackageManager {
}
@Override
- public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
- String installerPackageName) {
- installCommon(packageURI, new LegacyPackageInstallObserver(observer), flags,
- installerPackageName, mContext.getUserId());
- }
-
- @Override
- public void installPackage(Uri packageURI, PackageInstallObserver observer,
- int flags, String installerPackageName) {
- installCommon(packageURI, observer, flags, installerPackageName, mContext.getUserId());
- }
-
- private void installCommon(Uri packageURI,
- PackageInstallObserver observer, int flags, String installerPackageName,
- int userId) {
+ public void installPackage(Uri packageURI,
+ PackageInstallObserver observer, int flags, String installerPackageName) {
if (!"file".equals(packageURI.getScheme())) {
throw new UnsupportedOperationException("Only file:// URIs are supported");
}
@@ -1703,7 +1689,7 @@ public class ApplicationPackageManager extends PackageManager {
final String originPath = packageURI.getPath();
try {
mPM.installPackageAsUser(originPath, observer.getBinder(), flags, installerPackageName,
- userId);
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2476,7 +2462,8 @@ public class ApplicationPackageManager extends PackageManager {
if (itemInfo.showUserIcon != UserHandle.USER_NULL) {
Bitmap bitmap = getUserManager().getUserIcon(itemInfo.showUserIcon);
if (bitmap == null) {
- return UserIcons.getDefaultUserIcon(itemInfo.showUserIcon, /* light= */ false);
+ return UserIcons.getDefaultUserIcon(
+ mContext.getResources(), itemInfo.showUserIcon, /* light= */ false);
}
return new BitmapDrawable(bitmap);
}
diff --git a/android/app/ClientTransactionHandler.java b/android/app/ClientTransactionHandler.java
new file mode 100644
index 00000000..f7f4c716
--- /dev/null
+++ b/android/app/ClientTransactionHandler.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app;
+
+import android.app.servertransaction.ClientTransaction;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+
+/**
+ * Defines operations that a {@link android.app.servertransaction.ClientTransaction} or its items
+ * can perform on client.
+ * @hide
+ */
+public abstract class ClientTransactionHandler {
+
+ // Schedule phase related logic and handlers.
+
+ /** Prepare and schedule transaction for execution. */
+ void scheduleTransaction(ClientTransaction transaction) {
+ transaction.prepare(this);
+ sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
+ }
+
+ abstract void sendMessage(int what, Object obj);
+
+
+ // Prepare phase related logic and handlers. Methods that inform about about pending changes or
+ // do other internal bookkeeping.
+
+ /** Get current lifecycle request number to maintain correct ordering. */
+ public abstract int getLifecycleSeq();
+
+ /** Set pending config in case it will be updated by other transaction item. */
+ public abstract void updatePendingConfiguration(Configuration config);
+
+ /** Set current process state. */
+ public abstract void updateProcessState(int processState, boolean fromIpc);
+
+
+ // Execute phase related logic and handlers. Methods here execute actual lifecycle transactions
+ // and deliver callbacks.
+
+ /** Destroy the activity. */
+ public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+ boolean getNonConfigInstance);
+
+ /** Pause the activity. */
+ public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
+ int configChanges, boolean dontReport, int seq);
+
+ /** Resume the activity. */
+ public abstract void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
+ boolean reallyResume, int seq, String reason);
+
+ /** Stop the activity. */
+ public abstract void handleStopActivity(IBinder token, boolean show, int configChanges,
+ int seq);
+
+ /** Deliver activity (override) configuration change. */
+ public abstract void handleActivityConfigurationChanged(IBinder activityToken,
+ Configuration overrideConfig, int displayId);
+
+ /** Deliver result from another activity. */
+ public abstract void handleSendResult(IBinder token, List<ResultInfo> results);
+
+ /** Deliver multi-window mode change notification. */
+ public abstract void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
+ Configuration overrideConfig);
+
+ /** Deliver new intent. */
+ public abstract void handleNewIntent(IBinder token, List<ReferrerIntent> intents,
+ boolean andPause);
+
+ /** Deliver picture-in-picture mode change notification. */
+ public abstract void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
+ Configuration overrideConfig);
+
+ /** Update window visibility. */
+ public abstract void handleWindowVisibility(IBinder token, boolean show);
+
+ /** Perform activity launch. */
+ public abstract void handleLaunchActivity(IBinder token, Intent intent, int ident,
+ ActivityInfo info, Configuration overrideConfig, CompatibilityInfo compatInfo,
+ String referrer, IVoiceInteractor voiceInteractor, Bundle state,
+ PersistableBundle persistentState, List<ResultInfo> pendingResults,
+ List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
+ ProfilerInfo profilerInfo);
+
+ /** Deliver app configuration change notification. */
+ public abstract void handleConfigurationChanged(Configuration config);
+}
diff --git a/android/app/ContextImpl.java b/android/app/ContextImpl.java
index 5f343226..b0d020a7 100644
--- a/android/app/ContextImpl.java
+++ b/android/app/ContextImpl.java
@@ -59,11 +59,10 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -2457,7 +2456,8 @@ class ContextImpl extends Context {
* unable to create, they are filtered by replacing with {@code null}.
*/
private File[] ensureExternalDirsExistOrFilter(File[] dirs) {
- File[] result = new File[dirs.length];
+ final StorageManager sm = getSystemService(StorageManager.class);
+ final File[] result = new File[dirs.length];
for (int i = 0; i < dirs.length; i++) {
File dir = dirs[i];
if (!dir.exists()) {
@@ -2466,15 +2466,8 @@ class ContextImpl extends Context {
if (!dir.exists()) {
// Failing to mkdir() may be okay, since we might not have
// enough permissions; ask vold to create on our behalf.
- final IStorageManager storageManager = IStorageManager.Stub.asInterface(
- ServiceManager.getService("mount"));
try {
- final int res = storageManager.mkdirs(
- getPackageName(), dir.getAbsolutePath());
- if (res != 0) {
- Log.w(TAG, "Failed to ensure " + dir + ": " + res);
- dir = null;
- }
+ sm.mkdirs(dir);
} catch (Exception e) {
Log.w(TAG, "Failed to ensure " + dir + ": " + e);
dir = null;
diff --git a/android/app/Notification.java b/android/app/Notification.java
index d5d95fb8..42c1347e 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -68,6 +68,7 @@ import android.text.style.TextAppearanceSpan;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import android.view.Gravity;
import android.view.NotificationHeaderView;
import android.view.View;
@@ -2447,6 +2448,30 @@ public class Notification implements Parcelable
notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
}
+ /**
+ * @hide
+ */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(NotificationProto.CHANNEL_ID, getChannelId());
+ proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null);
+ proto.write(NotificationProto.FLAGS, this.flags);
+ proto.write(NotificationProto.COLOR, this.color);
+ proto.write(NotificationProto.CATEGORY, this.category);
+ proto.write(NotificationProto.GROUP_KEY, this.mGroupKey);
+ proto.write(NotificationProto.SORT_KEY, this.mSortKey);
+ if (this.actions != null) {
+ proto.write(NotificationProto.ACTION_LENGTH, this.actions.length);
+ }
+ if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) {
+ proto.write(NotificationProto.VISIBILITY, this.visibility);
+ }
+ if (publicVersion != null) {
+ publicVersion.writeToProto(proto, NotificationProto.PUBLIC_VERSION);
+ }
+ proto.end(token);
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java
index f931589b..659cf169 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -93,6 +93,58 @@ public class NotificationManager {
private static boolean localLOGV = false;
/**
+ * Intent that is broadcast when a {@link NotificationChannel} is blocked
+ * (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked
+ * (when {@link NotificationChannel#getImportance()} is anything other than
+ * {@link #IMPORTANCE_NONE}).
+ *
+ * This broadcast is only sent to the app that owns the channel that has changed.
+ *
+ * Input: nothing
+ * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID}
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED =
+ "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+
+ /**
+ * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or
+ * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the id of the
+ * object which has a new blocked state.
+ *
+ * The value will be the {@link NotificationChannel#getId()} of the channel for
+ * {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} and
+ * the {@link NotificationChannelGroup#getId()} of the group for
+ * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED}.
+ */
+ public static final String EXTRA_BLOCK_STATE_CHANGED_ID =
+ "android.app.extra.BLOCK_STATE_CHANGED_ID";
+
+ /**
+ * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or
+ * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the new blocked
+ * state as a boolean.
+ *
+ * The value will be {@code true} if this channel or group is now blocked and {@code false} if
+ * this channel or group is now unblocked.
+ */
+ public static final String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+
+
+ /**
+ * Intent that is broadcast when a {@link NotificationChannelGroup} is
+ * {@link NotificationChannelGroup#isBlocked() blocked} or unblocked.
+ *
+ * This broadcast is only sent to the app that owns the channel group that has changed.
+ *
+ * Input: nothing
+ * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID}
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED =
+ "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
+
+ /**
* Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes.
* This broadcast is only sent to registered receivers.
*
@@ -504,6 +556,20 @@ public class NotificationManager {
}
/**
+ * Returns the notification channel group settings for a given channel group id.
+ *
+ * The channel group must belong to your package, or null will be returned.
+ */
+ public NotificationChannelGroup getNotificationChannelGroup(String channelGroupId) {
+ INotificationManager service = getService();
+ try {
+ return service.getNotificationChannelGroup(mContext.getPackageName(), channelGroupId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns all notification channel groups belonging to the calling app.
*/
public List<NotificationChannelGroup> getNotificationChannelGroups() {
diff --git a/android/app/ProfilerInfo.java b/android/app/ProfilerInfo.java
index fad4798e..d5234278 100644
--- a/android/app/ProfilerInfo.java
+++ b/android/app/ProfilerInfo.java
@@ -22,6 +22,7 @@ import android.os.Parcelable;
import android.util.Slog;
import java.io.IOException;
+import java.util.Objects;
/**
* System private API for passing profiler settings.
@@ -132,4 +133,32 @@ public class ProfilerInfo implements Parcelable {
streamingOutput = in.readInt() != 0;
agent = in.readString();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ProfilerInfo other = (ProfilerInfo) o;
+ // TODO: Also check #profileFd for equality.
+ return Objects.equals(profileFile, other.profileFile)
+ && autoStopProfiler == other.autoStopProfiler
+ && samplingInterval == other.samplingInterval
+ && streamingOutput == other.streamingOutput
+ && Objects.equals(agent, other.agent);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(profileFile);
+ result = 31 * result + samplingInterval;
+ result = 31 * result + (autoStopProfiler ? 1 : 0);
+ result = 31 * result + (streamingOutput ? 1 : 0);
+ result = 31 * result + Objects.hashCode(agent);
+ return result;
+ }
}
diff --git a/android/app/ResultInfo.java b/android/app/ResultInfo.java
index 5e0867c3..d5af08a6 100644
--- a/android/app/ResultInfo.java
+++ b/android/app/ResultInfo.java
@@ -20,6 +20,8 @@ import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* {@hide}
*/
@@ -79,4 +81,29 @@ public class ResultInfo implements Parcelable {
mData = null;
}
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof ResultInfo)) {
+ return false;
+ }
+ final ResultInfo other = (ResultInfo) obj;
+ final boolean intentsEqual = mData == null ? (other.mData == null)
+ : mData.filterEquals(other.mData);
+ return intentsEqual && Objects.equals(mResultWho, other.mResultWho)
+ && mResultCode == other.mResultCode
+ && mRequestCode == other.mRequestCode;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mRequestCode;
+ result = 31 * result + mResultCode;
+ result = 31 * result + Objects.hashCode(mResultWho);
+ if (mData != null) {
+ result = 31 * result + mData.filterHashCode();
+ }
+ return result;
+ }
}
diff --git a/android/app/SharedPreferencesImpl.java b/android/app/SharedPreferencesImpl.java
index 6ea08252..8c47598f 100644
--- a/android/app/SharedPreferencesImpl.java
+++ b/android/app/SharedPreferencesImpl.java
@@ -34,8 +34,6 @@ import dalvik.system.BlockGuard;
import libcore.io.IoUtils;
-import com.google.android.collect.Maps;
-
import org.xmlpull.v1.XmlPullParserException;
import java.io.BufferedInputStream;
@@ -139,7 +137,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
}
- Map map = null;
+ Map<String, Object> map = null;
StructStat stat = null;
try {
stat = Os.stat(mFile.getPath());
@@ -148,7 +146,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16*1024);
- map = XmlUtils.readMapXml(str);
+ map = (Map<String, Object>) XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
@@ -214,12 +212,14 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(mLock) {
mListeners.put(listener, CONTENT);
}
}
+ @Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(mLock) {
mListeners.remove(listener);
@@ -241,6 +241,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
public Map<String, ?> getAll() {
synchronized (mLock) {
awaitLoadedLocked();
@@ -249,6 +250,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
@@ -258,6 +260,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
@Nullable
public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
synchronized (mLock) {
@@ -267,6 +270,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
public int getInt(String key, int defValue) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -274,6 +278,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
return v != null ? v : defValue;
}
}
+ @Override
public long getLong(String key, long defValue) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -281,6 +286,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
return v != null ? v : defValue;
}
}
+ @Override
public float getFloat(String key, float defValue) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -288,6 +294,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
return v != null ? v : defValue;
}
}
+ @Override
public boolean getBoolean(String key, boolean defValue) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -296,6 +303,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
public boolean contains(String key) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -303,6 +311,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
@@ -347,71 +356,81 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
public final class EditorImpl implements Editor {
- private final Object mLock = new Object();
+ private final Object mEditorLock = new Object();
- @GuardedBy("mLock")
- private final Map<String, Object> mModified = Maps.newHashMap();
+ @GuardedBy("mEditorLock")
+ private final Map<String, Object> mModified = new HashMap<>();
- @GuardedBy("mLock")
+ @GuardedBy("mEditorLock")
private boolean mClear = false;
+ @Override
public Editor putString(String key, @Nullable String value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor putStringSet(String key, @Nullable Set<String> values) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key,
(values == null) ? null : new HashSet<String>(values));
return this;
}
}
+ @Override
public Editor putInt(String key, int value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor putLong(String key, long value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor putFloat(String key, float value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor putBoolean(String key, boolean value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor remove(String key) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, this);
return this;
}
}
+ @Override
public Editor clear() {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mClear = true;
return this;
}
}
+ @Override
public void apply() {
final long startTime = System.currentTimeMillis();
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
+ @Override
public void run() {
try {
mcr.writtenToDiskLatch.await();
@@ -429,6 +448,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
+ @Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
@@ -471,13 +491,13 @@ final class SharedPreferencesImpl implements SharedPreferences {
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
- synchronized (mLock) {
+ synchronized (mEditorLock) {
boolean changesMade = false;
if (mClear) {
- if (!mMap.isEmpty()) {
+ if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
- mMap.clear();
+ mapToWriteToDisk.clear();
}
mClear = false;
}
@@ -489,18 +509,18 @@ final class SharedPreferencesImpl implements SharedPreferences {
// setting a value to "null" for a given key is specified to be
// equivalent to calling remove on that key.
if (v == this || v == null) {
- if (!mMap.containsKey(k)) {
+ if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
- mMap.remove(k);
+ mapToWriteToDisk.remove(k);
} else {
- if (mMap.containsKey(k)) {
- Object existingValue = mMap.get(k);
+ if (mapToWriteToDisk.containsKey(k)) {
+ Object existingValue = mapToWriteToDisk.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
- mMap.put(k, v);
+ mapToWriteToDisk.put(k, v);
}
changesMade = true;
@@ -522,6 +542,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
mapToWriteToDisk);
}
+ @Override
public boolean commit() {
long startTime = 0;
@@ -564,11 +585,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
} else {
// Run this function on the main thread.
- ActivityThread.sMainThreadHandler.post(new Runnable() {
- public void run() {
- notifyListeners(mcr);
- }
- });
+ ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr));
}
}
}
@@ -594,6 +611,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
+ @Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
@@ -646,7 +664,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
return str;
}
- // Note: must hold mWritingToDiskLock
+ @GuardedBy("mWritingToDiskLock")
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
long startTime = 0;
long existsTime = 0;
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index 2c1fad1c..80399ae6 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -40,6 +40,13 @@ import android.view.DisplayInfo;
*/
@TestApi
public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> {
+ /**
+ * bounds that can differ from app bounds, which may include things such as insets.
+ *
+ * TODO: Investigate combining with {@link mAppBounds}. Can the latter be a product of the
+ * former?
+ */
+ private Rect mBounds = new Rect();
/**
* {@link android.graphics.Rect} defining app bounds. The dimensions override usages of
@@ -117,22 +124,26 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
})
public @interface ActivityType {}
+ /** Bit that indicates that the {@link #mBounds} changed.
+ * @hide */
+ public static final int WINDOW_CONFIG_BOUNDS = 1 << 0;
/** Bit that indicates that the {@link #mAppBounds} changed.
* @hide */
- public static final int WINDOW_CONFIG_APP_BOUNDS = 1 << 0;
+ public static final int WINDOW_CONFIG_APP_BOUNDS = 1 << 1;
/** Bit that indicates that the {@link #mWindowingMode} changed.
* @hide */
- public static final int WINDOW_CONFIG_WINDOWING_MODE = 1 << 1;
+ public static final int WINDOW_CONFIG_WINDOWING_MODE = 1 << 2;
/** Bit that indicates that the {@link #mActivityType} changed.
* @hide */
- public static final int WINDOW_CONFIG_ACTIVITY_TYPE = 1 << 2;
+ public static final int WINDOW_CONFIG_ACTIVITY_TYPE = 1 << 3;
/** @hide */
@IntDef(flag = true,
value = {
+ WINDOW_CONFIG_BOUNDS,
WINDOW_CONFIG_APP_BOUNDS,
WINDOW_CONFIG_WINDOWING_MODE,
- WINDOW_CONFIG_ACTIVITY_TYPE,
+ WINDOW_CONFIG_ACTIVITY_TYPE
})
public @interface WindowConfig {}
@@ -151,12 +162,14 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mBounds, flags);
dest.writeParcelable(mAppBounds, flags);
dest.writeInt(mWindowingMode);
dest.writeInt(mActivityType);
}
private void readFromParcel(Parcel source) {
+ mBounds = source.readParcelable(Rect.class.getClassLoader());
mAppBounds = source.readParcelable(Rect.class.getClassLoader());
mWindowingMode = source.readInt();
mActivityType = source.readInt();
@@ -181,6 +194,19 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
};
/**
+ * Sets the bounds to the provided {@link Rect}.
+ * @param rect the new bounds value.
+ */
+ public void setBounds(Rect rect) {
+ if (rect == null) {
+ mBounds.setEmpty();
+ return;
+ }
+
+ mBounds.set(rect);
+ }
+
+ /**
* Set {@link #mAppBounds} to the input Rect.
* @param rect The rect value to set {@link #mAppBounds} to.
* @see #getAppBounds()
@@ -212,6 +238,11 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
return mAppBounds;
}
+ /** @see #setBounds(Rect) */
+ public Rect getBounds() {
+ return mBounds;
+ }
+
public void setWindowingMode(@WindowingMode int windowingMode) {
mWindowingMode = windowingMode;
}
@@ -244,6 +275,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
}
public void setTo(WindowConfiguration other) {
+ setBounds(other.mBounds);
setAppBounds(other.mAppBounds);
setWindowingMode(other.mWindowingMode);
setActivityType(other.mActivityType);
@@ -258,6 +290,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
/** @hide */
public void setToDefaults() {
setAppBounds(null);
+ setBounds(null);
setWindowingMode(WINDOWING_MODE_UNDEFINED);
setActivityType(ACTIVITY_TYPE_UNDEFINED);
}
@@ -272,6 +305,11 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
*/
public @WindowConfig int updateFrom(@NonNull WindowConfiguration delta) {
int changed = 0;
+ // Only allow override if bounds is not empty
+ if (!delta.mBounds.isEmpty() && !delta.mBounds.equals(mBounds)) {
+ changed |= WINDOW_CONFIG_BOUNDS;
+ setBounds(delta.mBounds);
+ }
if (delta.mAppBounds != null && !delta.mAppBounds.equals(mAppBounds)) {
changed |= WINDOW_CONFIG_APP_BOUNDS;
setAppBounds(delta.mAppBounds);
@@ -303,6 +341,10 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
public @WindowConfig long diff(WindowConfiguration other, boolean compareUndefined) {
long changes = 0;
+ if (!mBounds.equals(other.mBounds)) {
+ changes |= WINDOW_CONFIG_BOUNDS;
+ }
+
// Make sure that one of the values is not null and that they are not equal.
if ((compareUndefined || other.mAppBounds != null)
&& mAppBounds != other.mAppBounds
@@ -340,6 +382,16 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
n = mAppBounds.bottom - that.mAppBounds.bottom;
if (n != 0) return n;
}
+
+ n = mBounds.left - that.mBounds.left;
+ if (n != 0) return n;
+ n = mBounds.top - that.mBounds.top;
+ if (n != 0) return n;
+ n = mBounds.right - that.mBounds.right;
+ if (n != 0) return n;
+ n = mBounds.bottom - that.mBounds.bottom;
+ if (n != 0) return n;
+
n = mWindowingMode - that.mWindowingMode;
if (n != 0) return n;
n = mActivityType - that.mActivityType;
@@ -367,6 +419,8 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
if (mAppBounds != null) {
result = 31 * result + mAppBounds.hashCode();
}
+ result = 31 * result + mBounds.hashCode();
+
result = 31 * result + mWindowingMode;
result = 31 * result + mActivityType;
return result;
@@ -375,7 +429,8 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
/** @hide */
@Override
public String toString() {
- return "{mAppBounds=" + mAppBounds
+ return "{ mBounds=" + mBounds
+ + " mAppBounds=" + mAppBounds
+ " mWindowingMode=" + windowingModeToString(mWindowingMode)
+ " mActivityType=" + activityTypeToString(mActivityType) + "}";
}
diff --git a/android/app/admin/ConnectEvent.java b/android/app/admin/ConnectEvent.java
index ffd38e2b..f06a9257 100644
--- a/android/app/admin/ConnectEvent.java
+++ b/android/app/admin/ConnectEvent.java
@@ -32,29 +32,30 @@ import java.net.UnknownHostException;
public final class ConnectEvent extends NetworkEvent implements Parcelable {
/** The destination IP address. */
- private final String ipAddress;
+ private final String mIpAddress;
/** The destination port number. */
- private final int port;
+ private final int mPort;
/** @hide */
public ConnectEvent(String ipAddress, int port, String packageName, long timestamp) {
super(packageName, timestamp);
- this.ipAddress = ipAddress;
- this.port = port;
+ this.mIpAddress = ipAddress;
+ this.mPort = port;
}
private ConnectEvent(Parcel in) {
- this.ipAddress = in.readString();
- this.port = in.readInt();
- this.packageName = in.readString();
- this.timestamp = in.readLong();
+ this.mIpAddress = in.readString();
+ this.mPort = in.readInt();
+ this.mPackageName = in.readString();
+ this.mTimestamp = in.readLong();
+ this.mId = in.readLong();
}
public InetAddress getInetAddress() {
try {
// ipAddress is already an address, not a host name, no DNS resolution will happen.
- return InetAddress.getByName(ipAddress);
+ return InetAddress.getByName(mIpAddress);
} catch (UnknownHostException e) {
// Should never happen as we aren't passing a host name.
return InetAddress.getLoopbackAddress();
@@ -62,13 +63,13 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable {
}
public int getPort() {
- return port;
+ return mPort;
}
@Override
public String toString() {
- return String.format("ConnectEvent(%s, %d, %d, %s)", ipAddress, port, timestamp,
- packageName);
+ return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp,
+ mPackageName);
}
public static final Parcelable.Creator<ConnectEvent> CREATOR
@@ -96,10 +97,10 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
// write parcel token first
out.writeInt(PARCEL_TOKEN_CONNECT_EVENT);
- out.writeString(ipAddress);
- out.writeInt(port);
- out.writeString(packageName);
- out.writeLong(timestamp);
+ out.writeString(mIpAddress);
+ out.writeInt(mPort);
+ out.writeString(mPackageName);
+ out.writeLong(mTimestamp);
+ out.writeLong(mId);
}
}
-
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index f0226b7e..0bca9690 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -3858,6 +3858,47 @@ public class DevicePolicyManager {
*/
public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
@NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) {
+ return installKeyPair(admin, privKey, certs, alias, requestAccess, true);
+ }
+
+ /**
+ * Called by a device or profile owner, or delegated certificate installer, to install a
+ * certificate chain and corresponding private key for the leaf certificate. All apps within the
+ * profile will be able to access the certificate chain and use the private key, given direct
+ * user approval (if the user is allowed to select the private key).
+ *
+ * <p>The caller of this API may grant itself access to the certificate and private key
+ * immediately, without user approval. It is a best practice not to request this unless strictly
+ * necessary since it opens up additional security vulnerabilities.
+ *
+ * <p>Whether this key is offered to the user for approval at all or not depends on the
+ * {@code isUserSelectable} parameter.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if calling from a delegated certificate installer.
+ * @param privKey The private key to install.
+ * @param certs The certificate chain to install. The chain should start with the leaf
+ * certificate and include the chain of trust in order. This will be returned by
+ * {@link android.security.KeyChain#getCertificateChain}.
+ * @param alias The private key alias under which to install the certificate. If a certificate
+ * with that alias already exists, it will be overwritten.
+ * @param requestAccess {@code true} to request that the calling app be granted access to the
+ * credentials immediately. Otherwise, access to the credentials will be gated by user
+ * approval.
+ * @param isUserSelectable {@code true} to indicate that a user can select this key via the
+ * Certificate Selection prompt, false to indicate that this key can only be granted
+ * access by implementing
+ * {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+ * @return {@code true} if the keys were installed, {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+ * owner.
+ * @see android.security.KeyChain#getCertificateChain
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
+ */
+ public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
+ @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess,
+ boolean isUserSelectable) {
throwIfParentInstance("installKeyPair");
try {
final byte[] pemCert = Credentials.convertToPem(certs[0]);
@@ -3868,7 +3909,7 @@ public class DevicePolicyManager {
final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm())
.getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded();
return mService.installKeyPair(admin, mContext.getPackageName(), pkcs8Key, pemCert,
- pemChain, alias, requestAccess);
+ pemChain, alias, requestAccess, isUserSelectable);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
diff --git a/android/app/admin/DnsEvent.java b/android/app/admin/DnsEvent.java
index f84c5b00..4ddf13e0 100644
--- a/android/app/admin/DnsEvent.java
+++ b/android/app/admin/DnsEvent.java
@@ -34,46 +34,47 @@ import java.util.List;
public final class DnsEvent extends NetworkEvent implements Parcelable {
/** The hostname that was looked up. */
- private final String hostname;
+ private final String mHostname;
/** Contains (possibly a subset of) the IP addresses returned. */
- private final String[] ipAddresses;
+ private final String[] mIpAddresses;
/**
* The number of IP addresses returned from the DNS lookup event. May be different from the
* length of ipAddresses if there were too many addresses to log.
*/
- private final int ipAddressesCount;
+ private final int mIpAddressesCount;
/** @hide */
public DnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount,
String packageName, long timestamp) {
super(packageName, timestamp);
- this.hostname = hostname;
- this.ipAddresses = ipAddresses;
- this.ipAddressesCount = ipAddressesCount;
+ this.mHostname = hostname;
+ this.mIpAddresses = ipAddresses;
+ this.mIpAddressesCount = ipAddressesCount;
}
private DnsEvent(Parcel in) {
- this.hostname = in.readString();
- this.ipAddresses = in.createStringArray();
- this.ipAddressesCount = in.readInt();
- this.packageName = in.readString();
- this.timestamp = in.readLong();
+ this.mHostname = in.readString();
+ this.mIpAddresses = in.createStringArray();
+ this.mIpAddressesCount = in.readInt();
+ this.mPackageName = in.readString();
+ this.mTimestamp = in.readLong();
+ this.mId = in.readLong();
}
/** Returns the hostname that was looked up. */
public String getHostname() {
- return hostname;
+ return mHostname;
}
/** Returns (possibly a subset of) the IP addresses returned. */
public List<InetAddress> getInetAddresses() {
- if (ipAddresses == null || ipAddresses.length == 0) {
+ if (mIpAddresses == null || mIpAddresses.length == 0) {
return Collections.emptyList();
}
- final List<InetAddress> inetAddresses = new ArrayList<>(ipAddresses.length);
- for (final String ipAddress : ipAddresses) {
+ final List<InetAddress> inetAddresses = new ArrayList<>(mIpAddresses.length);
+ for (final String ipAddress : mIpAddresses) {
try {
// ipAddress is already an address, not a host name, no DNS resolution will happen.
inetAddresses.add(InetAddress.getByName(ipAddress));
@@ -90,14 +91,14 @@ public final class DnsEvent extends NetworkEvent implements Parcelable {
* addresses to log.
*/
public int getTotalResolvedAddressCount() {
- return ipAddressesCount;
+ return mIpAddressesCount;
}
@Override
public String toString() {
- return String.format("DnsEvent(%s, %s, %d, %d, %s)", hostname,
- (ipAddresses == null) ? "NONE" : String.join(" ", ipAddresses),
- ipAddressesCount, timestamp, packageName);
+ return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname,
+ (mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses),
+ mIpAddressesCount, mTimestamp, mPackageName);
}
public static final Parcelable.Creator<DnsEvent> CREATOR
@@ -125,11 +126,11 @@ public final class DnsEvent extends NetworkEvent implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
// write parcel token first
out.writeInt(PARCEL_TOKEN_DNS_EVENT);
- out.writeString(hostname);
- out.writeStringArray(ipAddresses);
- out.writeInt(ipAddressesCount);
- out.writeString(packageName);
- out.writeLong(timestamp);
+ out.writeString(mHostname);
+ out.writeStringArray(mIpAddresses);
+ out.writeInt(mIpAddressesCount);
+ out.writeString(mPackageName);
+ out.writeLong(mTimestamp);
+ out.writeLong(mId);
}
}
-
diff --git a/android/app/admin/NetworkEvent.java b/android/app/admin/NetworkEvent.java
index 2646c3fd..947e4fed 100644
--- a/android/app/admin/NetworkEvent.java
+++ b/android/app/admin/NetworkEvent.java
@@ -18,8 +18,8 @@ package android.app.admin;
import android.content.pm.PackageManager;
import android.os.Parcel;
-import android.os.Parcelable;
import android.os.ParcelFormatException;
+import android.os.Parcelable;
/**
* An abstract class that represents a network event.
@@ -32,10 +32,13 @@ public abstract class NetworkEvent implements Parcelable {
static final int PARCEL_TOKEN_CONNECT_EVENT = 2;
/** The package name of the UID that performed the query. */
- String packageName;
+ String mPackageName;
/** The timestamp of the event being reported in milliseconds. */
- long timestamp;
+ long mTimestamp;
+
+ /** The id of the event. */
+ long mId;
/** @hide */
NetworkEvent() {
@@ -44,8 +47,8 @@ public abstract class NetworkEvent implements Parcelable {
/** @hide */
NetworkEvent(String packageName, long timestamp) {
- this.packageName = packageName;
- this.timestamp = timestamp;
+ this.mPackageName = packageName;
+ this.mTimestamp = timestamp;
}
/**
@@ -53,7 +56,7 @@ public abstract class NetworkEvent implements Parcelable {
* {@link PackageManager#getNameForUid}.
*/
public String getPackageName() {
- return packageName;
+ return mPackageName;
}
/**
@@ -61,7 +64,20 @@ public abstract class NetworkEvent implements Parcelable {
* the time the event was reported and midnight, January 1, 1970 UTC.
*/
public long getTimestamp() {
- return timestamp;
+ return mTimestamp;
+ }
+
+ /** @hide */
+ public void setId(long id) {
+ this.mId = id;
+ }
+
+ /**
+ * Returns the id of the event, where the id monotonically increases for each event. The id
+ * is reset when the device reboots, and when network logging is enabled.
+ */
+ public long getId() {
+ return this.mId;
}
@Override
@@ -95,4 +111,3 @@ public abstract class NetworkEvent implements Parcelable {
@Override
public abstract void writeToParcel(Parcel out, int flags);
}
-
diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java
index e491a4f9..da5569d2 100644
--- a/android/app/assist/AssistStructure.java
+++ b/android/app/assist/AssistStructure.java
@@ -359,6 +359,8 @@ public class AssistStructure implements Parcelable {
if (DEBUG_PARCEL) Log.d(TAG, "Finished reading: at " + mCurParcel.dataPosition()
+ ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows
+ ", views=" + mNumReadViews);
+ mCurParcel.recycle();
+ mCurParcel = null; // Parcel cannot be used after recycled.
}
Parcel readParcel(int validateToken, int level) {
@@ -396,20 +398,23 @@ public class AssistStructure implements Parcelable {
private void fetchData() {
Parcel data = Parcel.obtain();
- data.writeInterfaceToken(DESCRIPTOR);
- data.writeStrongBinder(mTransferToken);
- if (DEBUG_PARCEL) Log.d(TAG, "Requesting data with token " + mTransferToken);
- if (mCurParcel != null) {
- mCurParcel.recycle();
- }
- mCurParcel = Parcel.obtain();
try {
- mChannel.transact(TRANSACTION_XFER, data, mCurParcel, 0);
- } catch (RemoteException e) {
- Log.w(TAG, "Failure reading AssistStructure data", e);
- throw new IllegalStateException("Failure reading AssistStructure data: " + e);
+ data.writeInterfaceToken(DESCRIPTOR);
+ data.writeStrongBinder(mTransferToken);
+ if (DEBUG_PARCEL) Log.d(TAG, "Requesting data with token " + mTransferToken);
+ if (mCurParcel != null) {
+ mCurParcel.recycle();
+ }
+ mCurParcel = Parcel.obtain();
+ try {
+ mChannel.transact(TRANSACTION_XFER, data, mCurParcel, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure reading AssistStructure data", e);
+ throw new IllegalStateException("Failure reading AssistStructure data: " + e);
+ }
+ } finally {
+ data.recycle();
}
- data.recycle();
mNumReadWindows = mNumReadViews = 0;
}
}
diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java
index 530d84b4..7c40b4ea 100644
--- a/android/app/job/JobInfo.java
+++ b/android/app/job/JobInfo.java
@@ -244,6 +244,13 @@ public class JobInfo implements Parcelable {
public static final int FLAG_WILL_BE_FOREGROUND = 1 << 0;
/**
+ * Allows this job to run despite doze restrictions as long as the app is in the foreground
+ * or on the temporary whitelist
+ * @hide
+ */
+ public static final int FLAG_IMPORTANT_WHILE_FOREGROUND = 1 << 1;
+
+ /**
* @hide
*/
public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
@@ -1333,6 +1340,30 @@ public class JobInfo implements Parcelable {
}
/**
+ * Setting this to true indicates that this job is important while the scheduling app
+ * is in the foreground or on the temporary whitelist for background restrictions.
+ * This means that the system will relax doze restrictions on this job during this time.
+ *
+ * Apps should use this flag only for short jobs that are essential for the app to function
+ * properly in the foreground.
+ *
+ * Note that once the scheduling app is no longer whitelisted from background restrictions
+ * and in the background, or the job failed due to unsatisfied constraints,
+ * this job should be expected to behave like other jobs without this flag.
+ *
+ * @param importantWhileForeground whether to relax doze restrictions for this job when the
+ * app is in the foreground. False by default.
+ */
+ public Builder setImportantWhileForeground(boolean importantWhileForeground) {
+ if (importantWhileForeground) {
+ mFlags |= FLAG_IMPORTANT_WHILE_FOREGROUND;
+ } else {
+ mFlags &= (~FLAG_IMPORTANT_WHILE_FOREGROUND);
+ }
+ return this;
+ }
+
+ /**
* Set whether or not to persist this job across device reboots.
*
* @param isPersisted True to indicate that the job will be written to
@@ -1395,6 +1426,10 @@ public class JobInfo implements Parcelable {
"persisted job");
}
}
+ if ((mFlags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0 && mHasEarlyConstraint) {
+ throw new IllegalArgumentException("An important while foreground job cannot "
+ + "have a time delay");
+ }
if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
throw new IllegalArgumentException("An idle mode job will not respect any" +
" back-off policy, so calling setBackoffCriteria with" +
diff --git a/android/app/job/JobService.java b/android/app/job/JobService.java
index 69afed20..61afadab 100644
--- a/android/app/job/JobService.java
+++ b/android/app/job/JobService.java
@@ -72,6 +72,33 @@ public abstract class JobService extends Service {
}
/**
+ * 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 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 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 wantsReschedule) {
+ mEngine.jobFinished(params, wantsReschedule);
+ }
+
+ /**
* 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.
@@ -127,31 +154,4 @@ public abstract class JobService extends Service {
* 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 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 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 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 wantsReschedule) {
- mEngine.jobFinished(params, wantsReschedule);
- }
}
diff --git a/android/app/servertransaction/ActivityConfigurationChangeItem.java b/android/app/servertransaction/ActivityConfigurationChangeItem.java
new file mode 100644
index 00000000..07001e2b
--- /dev/null
+++ b/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Activity configuration changed callback.
+ * @hide
+ */
+public class ActivityConfigurationChangeItem extends ClientTransactionItem {
+
+ private final Configuration mConfiguration;
+
+ public ActivityConfigurationChangeItem(Configuration configuration) {
+ mConfiguration = configuration;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
+ client.handleActivityConfigurationChanged(token, mConfiguration, INVALID_DISPLAY);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mConfiguration, flags);
+ }
+
+ /** Read from Parcel. */
+ private ActivityConfigurationChangeItem(Parcel in) {
+ mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<ActivityConfigurationChangeItem> CREATOR =
+ new Creator<ActivityConfigurationChangeItem>() {
+ public ActivityConfigurationChangeItem createFromParcel(Parcel in) {
+ return new ActivityConfigurationChangeItem(in);
+ }
+
+ public ActivityConfigurationChangeItem[] newArray(int size) {
+ return new ActivityConfigurationChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ActivityConfigurationChangeItem other = (ActivityConfigurationChangeItem) o;
+ return mConfiguration.equals(other.mConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ return mConfiguration.hashCode();
+ }
+}
diff --git a/android/app/servertransaction/ActivityLifecycleItem.java b/android/app/servertransaction/ActivityLifecycleItem.java
new file mode 100644
index 00000000..a64108db
--- /dev/null
+++ b/android/app/servertransaction/ActivityLifecycleItem.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Request for lifecycle state that an activity should reach.
+ * @hide
+ */
+public abstract class ActivityLifecycleItem extends ClientTransactionItem {
+
+ static final boolean DEBUG_ORDER = false;
+
+ @IntDef({UNDEFINED, RESUMED, PAUSED, STOPPED, DESTROYED})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface LifecycleState{}
+ public static final int UNDEFINED = -1;
+ public static final int RESUMED = 0;
+ public static final int PAUSED = 1;
+ public static final int STOPPED = 2;
+ public static final int DESTROYED = 3;
+
+ /** A final lifecycle state that an activity should reach. */
+ @LifecycleState
+ public abstract int getTargetState();
+}
diff --git a/android/app/servertransaction/ActivityResultItem.java b/android/app/servertransaction/ActivityResultItem.java
new file mode 100644
index 00000000..76664d8e
--- /dev/null
+++ b/android/app/servertransaction/ActivityResultItem.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.PAUSED;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ResultInfo;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Trace;
+
+import java.util.List;
+
+/**
+ * Activity result delivery callback.
+ * @hide
+ */
+public class ActivityResultItem extends ClientTransactionItem {
+
+ private final List<ResultInfo> mResultInfoList;
+
+ public ActivityResultItem(List<ResultInfo> resultInfos) {
+ mResultInfoList = resultInfos;
+ }
+
+ @Override
+ public int getPreExecutionState() {
+ return PAUSED;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
+ client.handleSendResult(token, mResultInfoList);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedList(mResultInfoList, flags);
+ }
+
+ /** Read from Parcel. */
+ private ActivityResultItem(Parcel in) {
+ mResultInfoList = in.createTypedArrayList(ResultInfo.CREATOR);
+ }
+
+ public static final Parcelable.Creator<ActivityResultItem> CREATOR =
+ new Parcelable.Creator<ActivityResultItem>() {
+ public ActivityResultItem createFromParcel(Parcel in) {
+ return new ActivityResultItem(in);
+ }
+
+ public ActivityResultItem[] newArray(int size) {
+ return new ActivityResultItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ActivityResultItem other = (ActivityResultItem) o;
+ return mResultInfoList.equals(other.mResultInfoList);
+ }
+
+ @Override
+ public int hashCode() {
+ return mResultInfoList.hashCode();
+ }
+}
diff --git a/android/app/servertransaction/BaseClientRequest.java b/android/app/servertransaction/BaseClientRequest.java
new file mode 100644
index 00000000..4bd01afb
--- /dev/null
+++ b/android/app/servertransaction/BaseClientRequest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+
+/**
+ * Base interface for individual requests from server to client.
+ * Each of them can be prepared before scheduling and, eventually, executed.
+ * @hide
+ */
+public interface BaseClientRequest {
+
+ /**
+ * Prepare the client request before scheduling.
+ * An example of this might be informing about pending updates for some values.
+ *
+ * @param client Target client handler.
+ * @param token Target activity token.
+ */
+ default void prepare(ClientTransactionHandler client, IBinder token) {
+ }
+
+ /**
+ * Execute the request.
+ * @param client Target client handler.
+ * @param token Target activity token.
+ */
+ void execute(ClientTransactionHandler client, IBinder token);
+}
diff --git a/android/app/servertransaction/ClientTransaction.java b/android/app/servertransaction/ClientTransaction.java
new file mode 100644
index 00000000..d2289ba0
--- /dev/null
+++ b/android/app/servertransaction/ClientTransaction.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.app.ClientTransactionHandler;
+import android.app.IApplicationThread;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A container that holds a sequence of messages, which may be sent to a client.
+ * This includes a list of callbacks and a final lifecycle state.
+ *
+ * @see com.android.server.am.ClientLifecycleManager
+ * @see ClientTransactionItem
+ * @see ActivityLifecycleItem
+ * @hide
+ */
+public class ClientTransaction implements Parcelable {
+
+ /** A list of individual callbacks to a client. */
+ private List<ClientTransactionItem> mActivityCallbacks;
+
+ /**
+ * Final lifecycle state in which the client activity should be after the transaction is
+ * executed.
+ */
+ private ActivityLifecycleItem mLifecycleStateRequest;
+
+ /** Target client. */
+ private IApplicationThread mClient;
+
+ /** Target client activity. Might be null if the entire transaction is targeting an app. */
+ private IBinder mActivityToken;
+
+ public ClientTransaction(IApplicationThread client, IBinder activityToken) {
+ mClient = client;
+ mActivityToken = activityToken;
+ }
+
+ /**
+ * Add a message to the end of the sequence of callbacks.
+ * @param activityCallback A single message that can contain a lifecycle request/callback.
+ */
+ public void addCallback(ClientTransactionItem activityCallback) {
+ if (mActivityCallbacks == null) {
+ mActivityCallbacks = new ArrayList<>();
+ }
+ mActivityCallbacks.add(activityCallback);
+ }
+
+ /**
+ * Set the lifecycle state in which the client should be after executing the transaction.
+ * @param stateRequest A lifecycle request initialized with right parameters.
+ */
+ public void setLifecycleStateRequest(ActivityLifecycleItem stateRequest) {
+ mLifecycleStateRequest = stateRequest;
+ }
+
+ /**
+ * Do what needs to be done while the transaction is being scheduled on the client side.
+ * @param clientTransactionHandler Handler on the client side that will executed all operations
+ * requested by transaction items.
+ */
+ public void prepare(android.app.ClientTransactionHandler clientTransactionHandler) {
+ if (mActivityCallbacks != null) {
+ final int size = mActivityCallbacks.size();
+ for (int i = 0; i < size; ++i) {
+ mActivityCallbacks.get(i).prepare(clientTransactionHandler, mActivityToken);
+ }
+ }
+ if (mLifecycleStateRequest != null) {
+ mLifecycleStateRequest.prepare(clientTransactionHandler, mActivityToken);
+ }
+ }
+
+ /**
+ * Execute the transaction.
+ * @param clientTransactionHandler Handler on the client side that will execute all operations
+ * requested by transaction items.
+ */
+ public void execute(android.app.ClientTransactionHandler clientTransactionHandler) {
+ if (mActivityCallbacks != null) {
+ final int size = mActivityCallbacks.size();
+ for (int i = 0; i < size; ++i) {
+ mActivityCallbacks.get(i).execute(clientTransactionHandler, mActivityToken);
+ }
+ }
+ if (mLifecycleStateRequest != null) {
+ mLifecycleStateRequest.execute(clientTransactionHandler, mActivityToken);
+ }
+ }
+
+ /**
+ * Schedule the transaction after it was initialized. It will be send to client and all its
+ * individual parts will be applied in the following sequence:
+ * 1. The client calls {@link #prepare(ClientTransactionHandler)}, which triggers all work that
+ * needs to be done before actually scheduling the transaction for callbacks and lifecycle
+ * state request.
+ * 2. The transaction message is scheduled.
+ * 3. The client calls {@link #execute(ClientTransactionHandler)}, which executes all callbacks
+ * and necessary lifecycle transitions.
+ */
+ public void schedule() throws RemoteException {
+ mClient.scheduleTransaction(this);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mClient.asBinder());
+ final boolean writeActivityToken = mActivityToken != null;
+ dest.writeBoolean(writeActivityToken);
+ if (writeActivityToken) {
+ dest.writeStrongBinder(mActivityToken);
+ }
+ dest.writeParcelable(mLifecycleStateRequest, flags);
+ final boolean writeActivityCallbacks = mActivityCallbacks != null;
+ dest.writeBoolean(writeActivityCallbacks);
+ if (writeActivityCallbacks) {
+ dest.writeParcelableList(mActivityCallbacks, flags);
+ }
+ }
+
+ /** Read from Parcel. */
+ private ClientTransaction(Parcel in) {
+ mClient = (IApplicationThread) in.readStrongBinder();
+ final boolean readActivityToken = in.readBoolean();
+ if (readActivityToken) {
+ mActivityToken = in.readStrongBinder();
+ }
+ mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader());
+ final boolean readActivityCallbacks = in.readBoolean();
+ if (readActivityCallbacks) {
+ mActivityCallbacks = new ArrayList<>();
+ in.readParcelableList(mActivityCallbacks, getClass().getClassLoader());
+ }
+ }
+
+ public static final Creator<ClientTransaction> CREATOR =
+ new Creator<ClientTransaction>() {
+ public ClientTransaction createFromParcel(Parcel in) {
+ return new ClientTransaction(in);
+ }
+
+ public ClientTransaction[] newArray(int size) {
+ return new ClientTransaction[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ClientTransaction other = (ClientTransaction) o;
+ return Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
+ && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest)
+ && mClient == other.mClient
+ && mActivityToken == other.mActivityToken;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(mActivityCallbacks);
+ result = 31 * result + Objects.hashCode(mLifecycleStateRequest);
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/ClientTransactionItem.java b/android/app/servertransaction/ClientTransactionItem.java
new file mode 100644
index 00000000..6f2cc007
--- /dev/null
+++ b/android/app/servertransaction/ClientTransactionItem.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
+import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
+
+import android.os.Parcelable;
+
+/**
+ * A callback message to a client that can be scheduled and executed.
+ * Examples of these might be activity configuration change, multi-window mode change, activity
+ * result delivery etc.
+ *
+ * @see ClientTransaction
+ * @see com.android.server.am.ClientLifecycleManager
+ * @hide
+ */
+public abstract class ClientTransactionItem implements BaseClientRequest, Parcelable {
+
+ /** Get the state in which this callback can be executed. */
+ @LifecycleState
+ public int getPreExecutionState() {
+ return UNDEFINED;
+ }
+
+ /** Get the state that must follow this callback. */
+ @LifecycleState
+ public int getPostExecutionState() {
+ return UNDEFINED;
+ }
+
+
+ // Parcelable
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/app/servertransaction/ConfigurationChangeItem.java b/android/app/servertransaction/ConfigurationChangeItem.java
new file mode 100644
index 00000000..055923ec
--- /dev/null
+++ b/android/app/servertransaction/ConfigurationChangeItem.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * App configuration change message.
+ * @hide
+ */
+public class ConfigurationChangeItem extends ClientTransactionItem {
+
+ private final Configuration mConfiguration;
+
+ public ConfigurationChangeItem(Configuration configuration) {
+ mConfiguration = new Configuration(configuration);
+ }
+
+ @Override
+ public void prepare(android.app.ClientTransactionHandler client, IBinder token) {
+ client.updatePendingConfiguration(mConfiguration);
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ client.handleConfigurationChanged(mConfiguration);
+ }
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mConfiguration, flags);
+ }
+
+ /** Read from Parcel. */
+ private ConfigurationChangeItem(Parcel in) {
+ mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<ConfigurationChangeItem> CREATOR =
+ new Creator<ConfigurationChangeItem>() {
+ public ConfigurationChangeItem createFromParcel(Parcel in) {
+ return new ConfigurationChangeItem(in);
+ }
+
+ public ConfigurationChangeItem[] newArray(int size) {
+ return new ConfigurationChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ConfigurationChangeItem other = (ConfigurationChangeItem) o;
+ return mConfiguration.equals(other.mConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ return mConfiguration.hashCode();
+ }
+}
diff --git a/android/app/servertransaction/DestroyActivityItem.java b/android/app/servertransaction/DestroyActivityItem.java
new file mode 100644
index 00000000..38fd5fb6
--- /dev/null
+++ b/android/app/servertransaction/DestroyActivityItem.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Request to destroy an activity.
+ * @hide
+ */
+public class DestroyActivityItem extends ActivityLifecycleItem {
+
+ private final boolean mFinished;
+ private final int mConfigChanges;
+
+ public DestroyActivityItem(boolean finished, int configChanges) {
+ mFinished = finished;
+ mConfigChanges = configChanges;
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
+ client.handleDestroyActivity(token, mFinished, mConfigChanges,
+ false /* getNonConfigInstance */);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return DESTROYED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mFinished);
+ dest.writeInt(mConfigChanges);
+ }
+
+ /** Read from Parcel. */
+ private DestroyActivityItem(Parcel in) {
+ mFinished = in.readBoolean();
+ mConfigChanges = in.readInt();
+ }
+
+ public static final Creator<DestroyActivityItem> CREATOR =
+ new Creator<DestroyActivityItem>() {
+ public DestroyActivityItem createFromParcel(Parcel in) {
+ return new DestroyActivityItem(in);
+ }
+
+ public DestroyActivityItem[] newArray(int size) {
+ return new DestroyActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final DestroyActivityItem other = (DestroyActivityItem) o;
+ return mFinished == other.mFinished && mConfigChanges == other.mConfigChanges;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mFinished ? 1 : 0);
+ result = 31 * result + mConfigChanges;
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/LaunchActivityItem.java b/android/app/servertransaction/LaunchActivityItem.java
new file mode 100644
index 00000000..417ebac8
--- /dev/null
+++ b/android/app/servertransaction/LaunchActivityItem.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.app.ProfilerInfo;
+import android.app.ResultInfo;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.os.Trace;
+
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request to launch an activity.
+ * @hide
+ */
+public class LaunchActivityItem extends ActivityLifecycleItem {
+
+ private final Intent mIntent;
+ private final int mIdent;
+ private final ActivityInfo mInfo;
+ private final Configuration mCurConfig;
+ private final Configuration mOverrideConfig;
+ private final CompatibilityInfo mCompatInfo;
+ private final String mReferrer;
+ private final IVoiceInteractor mVoiceInteractor;
+ private final int mProcState;
+ private final Bundle mState;
+ private final PersistableBundle mPersistentState;
+ private final List<ResultInfo> mPendingResults;
+ private final List<ReferrerIntent> mPendingNewIntents;
+ // TODO(lifecycler): use lifecycle request instead of this param.
+ private final boolean mNotResumed;
+ private final boolean mIsForward;
+ private final ProfilerInfo mProfilerInfo;
+
+ public LaunchActivityItem(Intent intent, int ident, ActivityInfo info,
+ Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo,
+ String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
+ PersistableBundle persistentState, List<ResultInfo> pendingResults,
+ List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
+ ProfilerInfo profilerInfo) {
+ mIntent = intent;
+ mIdent = ident;
+ mInfo = info;
+ mCurConfig = curConfig;
+ mOverrideConfig = overrideConfig;
+ mCompatInfo = compatInfo;
+ mReferrer = referrer;
+ mVoiceInteractor = voiceInteractor;
+ mProcState = procState;
+ mState = state;
+ mPersistentState = persistentState;
+ mPendingResults = pendingResults;
+ mPendingNewIntents = pendingNewIntents;
+ mNotResumed = notResumed;
+ mIsForward = isForward;
+ mProfilerInfo = profilerInfo;
+ }
+
+ @Override
+ public void prepare(ClientTransactionHandler client, IBinder token) {
+ client.updateProcessState(mProcState, false);
+ client.updatePendingConfiguration(mCurConfig);
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
+ client.handleLaunchActivity(token, mIntent, mIdent, mInfo, mOverrideConfig, mCompatInfo,
+ mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults,
+ mPendingNewIntents, mNotResumed, mIsForward, mProfilerInfo);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return mNotResumed ? PAUSED : RESUMED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write from Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mIntent, flags);
+ dest.writeInt(mIdent);
+ dest.writeTypedObject(mInfo, flags);
+ dest.writeTypedObject(mCurConfig, flags);
+ dest.writeTypedObject(mOverrideConfig, flags);
+ dest.writeTypedObject(mCompatInfo, flags);
+ dest.writeString(mReferrer);
+ dest.writeStrongBinder(mVoiceInteractor != null ? mVoiceInteractor.asBinder() : null);
+ dest.writeInt(mProcState);
+ dest.writeBundle(mState);
+ dest.writePersistableBundle(mPersistentState);
+ dest.writeTypedList(mPendingResults, flags);
+ dest.writeTypedList(mPendingNewIntents, flags);
+ dest.writeBoolean(mNotResumed);
+ dest.writeBoolean(mIsForward);
+ dest.writeTypedObject(mProfilerInfo, flags);
+ }
+
+ /** Read from Parcel. */
+ private LaunchActivityItem(Parcel in) {
+ mIntent = in.readTypedObject(Intent.CREATOR);
+ mIdent = in.readInt();
+ mInfo = in.readTypedObject(ActivityInfo.CREATOR);
+ mCurConfig = in.readTypedObject(Configuration.CREATOR);
+ mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
+ mCompatInfo = in.readTypedObject(CompatibilityInfo.CREATOR);
+ mReferrer = in.readString();
+ mVoiceInteractor = (IVoiceInteractor) in.readStrongBinder();
+ mProcState = in.readInt();
+ mState = in.readBundle(getClass().getClassLoader());
+ mPersistentState = in.readPersistableBundle(getClass().getClassLoader());
+ mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR);
+ mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
+ mNotResumed = in.readBoolean();
+ mIsForward = in.readBoolean();
+ mProfilerInfo = in.readTypedObject(ProfilerInfo.CREATOR);
+ }
+
+ public static final Creator<LaunchActivityItem> CREATOR =
+ new Creator<LaunchActivityItem>() {
+ public LaunchActivityItem createFromParcel(Parcel in) {
+ return new LaunchActivityItem(in);
+ }
+
+ public LaunchActivityItem[] newArray(int size) {
+ return new LaunchActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final LaunchActivityItem other = (LaunchActivityItem) o;
+ return mIntent.filterEquals(other.mIntent) && mIdent == other.mIdent
+ && activityInfoEqual(other.mInfo) && Objects.equals(mCurConfig, other.mCurConfig)
+ && Objects.equals(mOverrideConfig, other.mOverrideConfig)
+ && Objects.equals(mCompatInfo, other.mCompatInfo)
+ && Objects.equals(mReferrer, other.mReferrer)
+ && mProcState == other.mProcState && areBundlesEqual(mState, other.mState)
+ && areBundlesEqual(mPersistentState, other.mPersistentState)
+ && Objects.equals(mPendingResults, other.mPendingResults)
+ && Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
+ && mNotResumed == other.mNotResumed && mIsForward == other.mIsForward
+ && Objects.equals(mProfilerInfo, other.mProfilerInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mIntent.filterHashCode();
+ result = 31 * result + mIdent;
+ result = 31 * result + Objects.hashCode(mCurConfig);
+ result = 31 * result + Objects.hashCode(mOverrideConfig);
+ result = 31 * result + Objects.hashCode(mCompatInfo);
+ result = 31 * result + Objects.hashCode(mReferrer);
+ result = 31 * result + Objects.hashCode(mProcState);
+ result = 31 * result + (mState != null ? mState.size() : 0);
+ result = 31 * result + (mPersistentState != null ? mPersistentState.size() : 0);
+ result = 31 * result + Objects.hashCode(mPendingResults);
+ result = 31 * result + Objects.hashCode(mPendingNewIntents);
+ result = 31 * result + (mNotResumed ? 1 : 0);
+ result = 31 * result + (mIsForward ? 1 : 0);
+ result = 31 * result + Objects.hashCode(mProfilerInfo);
+ return result;
+ }
+
+ private boolean activityInfoEqual(ActivityInfo other) {
+ return mInfo.flags == other.flags && mInfo.maxAspectRatio == other.maxAspectRatio
+ && Objects.equals(mInfo.launchToken, other.launchToken)
+ && Objects.equals(mInfo.getComponentName(), other.getComponentName());
+ }
+
+ private static boolean areBundlesEqual(BaseBundle extras, BaseBundle newExtras) {
+ if (extras == null || newExtras == null) {
+ return extras == newExtras;
+ }
+
+ if (extras.size() != newExtras.size()) {
+ return false;
+ }
+
+ for (String key : extras.keySet()) {
+ if (key != null) {
+ final Object value = extras.get(key);
+ final Object newValue = newExtras.get(key);
+ if (!Objects.equals(value, newValue)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/android/app/servertransaction/MoveToDisplayItem.java b/android/app/servertransaction/MoveToDisplayItem.java
new file mode 100644
index 00000000..ccd80d88
--- /dev/null
+++ b/android/app/servertransaction/MoveToDisplayItem.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Activity move to a different display message.
+ * @hide
+ */
+public class MoveToDisplayItem extends ClientTransactionItem {
+
+ private final int mTargetDisplayId;
+ private final Configuration mConfiguration;
+
+ public MoveToDisplayItem(int targetDisplayId, Configuration configuration) {
+ mTargetDisplayId = targetDisplayId;
+ mConfiguration = configuration;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
+ client.handleActivityConfigurationChanged(token, mConfiguration, mTargetDisplayId);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTargetDisplayId);
+ dest.writeTypedObject(mConfiguration, flags);
+ }
+
+ /** Read from Parcel. */
+ private MoveToDisplayItem(Parcel in) {
+ mTargetDisplayId = in.readInt();
+ mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<MoveToDisplayItem> CREATOR = new Creator<MoveToDisplayItem>() {
+ public MoveToDisplayItem createFromParcel(Parcel in) {
+ return new MoveToDisplayItem(in);
+ }
+
+ public MoveToDisplayItem[] newArray(int size) {
+ return new MoveToDisplayItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final MoveToDisplayItem other = (MoveToDisplayItem) o;
+ return mTargetDisplayId == other.mTargetDisplayId
+ && mConfiguration.equals(other.mConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mTargetDisplayId;
+ result = 31 * result + mConfiguration.hashCode();
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/MultiWindowModeChangeItem.java b/android/app/servertransaction/MultiWindowModeChangeItem.java
new file mode 100644
index 00000000..a0c617fa
--- /dev/null
+++ b/android/app/servertransaction/MultiWindowModeChangeItem.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Multi-window mode change message.
+ * @hide
+ */
+// TODO(lifecycler): Remove the use of this and just use the configuration change message to
+// communicate multi-window mode change with WindowConfiguration.
+public class MultiWindowModeChangeItem extends ClientTransactionItem {
+
+ private final boolean mIsInMultiWindowMode;
+ private final Configuration mOverrideConfig;
+
+ public MultiWindowModeChangeItem(boolean isInMultiWindowMode,
+ Configuration overrideConfig) {
+ mIsInMultiWindowMode = isInMultiWindowMode;
+ mOverrideConfig = overrideConfig;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ client.handleMultiWindowModeChanged(token, mIsInMultiWindowMode, mOverrideConfig);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsInMultiWindowMode);
+ dest.writeTypedObject(mOverrideConfig, flags);
+ }
+
+ /** Read from Parcel. */
+ private MultiWindowModeChangeItem(Parcel in) {
+ mIsInMultiWindowMode = in.readBoolean();
+ mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<MultiWindowModeChangeItem> CREATOR =
+ new Creator<MultiWindowModeChangeItem>() {
+ public MultiWindowModeChangeItem createFromParcel(Parcel in) {
+ return new MultiWindowModeChangeItem(in);
+ }
+
+ public MultiWindowModeChangeItem[] newArray(int size) {
+ return new MultiWindowModeChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final MultiWindowModeChangeItem other = (MultiWindowModeChangeItem) o;
+ return mIsInMultiWindowMode == other.mIsInMultiWindowMode
+ && mOverrideConfig.equals(other.mOverrideConfig);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mIsInMultiWindowMode ? 1 : 0);
+ result = 31 * result + mOverrideConfig.hashCode();
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/NewIntentItem.java b/android/app/servertransaction/NewIntentItem.java
new file mode 100644
index 00000000..61a8965a
--- /dev/null
+++ b/android/app/servertransaction/NewIntentItem.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.PAUSED;
+import static android.app.servertransaction.ActivityLifecycleItem.RESUMED;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Trace;
+
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+
+/**
+ * New intent message.
+ * @hide
+ */
+public class NewIntentItem extends ClientTransactionItem {
+
+ private final List<ReferrerIntent> mIntents;
+ private final boolean mPause;
+
+ public NewIntentItem(List<ReferrerIntent> intents, boolean pause) {
+ mIntents = intents;
+ mPause = pause;
+ }
+
+ @Override
+ public int getPreExecutionState() {
+ return PAUSED;
+ }
+
+ @Override
+ public int getPostExecutionState() {
+ return RESUMED;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
+ client.handleNewIntent(token, mIntents, mPause);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mPause);
+ dest.writeTypedList(mIntents, flags);
+ }
+
+ /** Read from Parcel. */
+ private NewIntentItem(Parcel in) {
+ mPause = in.readBoolean();
+ mIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
+ }
+
+ public static final Parcelable.Creator<NewIntentItem> CREATOR =
+ new Parcelable.Creator<NewIntentItem>() {
+ public NewIntentItem createFromParcel(Parcel in) {
+ return new NewIntentItem(in);
+ }
+
+ public NewIntentItem[] newArray(int size) {
+ return new NewIntentItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final NewIntentItem other = (NewIntentItem) o;
+ return mPause == other.mPause && mIntents.equals(other.mIntents);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mPause ? 1 : 0);
+ result = 31 * result + mIntents.hashCode();
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/PauseActivityItem.java b/android/app/servertransaction/PauseActivityItem.java
new file mode 100644
index 00000000..e561a4b5
--- /dev/null
+++ b/android/app/servertransaction/PauseActivityItem.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * Request to move an activity to paused state.
+ * @hide
+ */
+public class PauseActivityItem extends ActivityLifecycleItem {
+
+ private static final String TAG = "PauseActivityItem";
+
+ private final boolean mFinished;
+ private final boolean mUserLeaving;
+ private final int mConfigChanges;
+ private final boolean mDontReport;
+
+ private int mLifecycleSeq;
+
+ public PauseActivityItem(boolean finished, boolean userLeaving, int configChanges,
+ boolean dontReport) {
+ mFinished = finished;
+ mUserLeaving = userLeaving;
+ mConfigChanges = configChanges;
+ mDontReport = dontReport;
+ }
+
+ @Override
+ public void prepare(ClientTransactionHandler client, IBinder token) {
+ mLifecycleSeq = client.getLifecycleSeq();
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Pause transaction for " + client + " received seq: "
+ + mLifecycleSeq);
+ }
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
+ client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, mDontReport,
+ mLifecycleSeq);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return PAUSED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mFinished);
+ dest.writeBoolean(mUserLeaving);
+ dest.writeInt(mConfigChanges);
+ dest.writeBoolean(mDontReport);
+ }
+
+ /** Read from Parcel. */
+ private PauseActivityItem(Parcel in) {
+ mFinished = in.readBoolean();
+ mUserLeaving = in.readBoolean();
+ mConfigChanges = in.readInt();
+ mDontReport = in.readBoolean();
+ }
+
+ public static final Creator<PauseActivityItem> CREATOR =
+ new Creator<PauseActivityItem>() {
+ public PauseActivityItem createFromParcel(Parcel in) {
+ return new PauseActivityItem(in);
+ }
+
+ public PauseActivityItem[] newArray(int size) {
+ return new PauseActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final PauseActivityItem other = (PauseActivityItem) o;
+ return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving
+ && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mFinished ? 1 : 0);
+ result = 31 * result + (mUserLeaving ? 1 : 0);
+ result = 31 * result + mConfigChanges;
+ result = 31 * result + (mDontReport ? 1 : 0);
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/PipModeChangeItem.java b/android/app/servertransaction/PipModeChangeItem.java
new file mode 100644
index 00000000..923839ee
--- /dev/null
+++ b/android/app/servertransaction/PipModeChangeItem.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Picture in picture mode change message.
+ * @hide
+ */
+// TODO(lifecycler): Remove the use of this and just use the configuration change message to
+// communicate multi-window mode change with WindowConfiguration.
+public class PipModeChangeItem extends ClientTransactionItem {
+
+ private final boolean mIsInPipMode;
+ private final Configuration mOverrideConfig;
+
+ public PipModeChangeItem(boolean isInPipMode, Configuration overrideConfig) {
+ mIsInPipMode = isInPipMode;
+ mOverrideConfig = overrideConfig;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ client.handlePictureInPictureModeChanged(token, mIsInPipMode, mOverrideConfig);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsInPipMode);
+ dest.writeTypedObject(mOverrideConfig, flags);
+ }
+
+ /** Read from Parcel. */
+ private PipModeChangeItem(Parcel in) {
+ mIsInPipMode = in.readBoolean();
+ mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<PipModeChangeItem> CREATOR =
+ new Creator<PipModeChangeItem>() {
+ public PipModeChangeItem createFromParcel(Parcel in) {
+ return new PipModeChangeItem(in);
+ }
+
+ public PipModeChangeItem[] newArray(int size) {
+ return new PipModeChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final PipModeChangeItem other = (PipModeChangeItem) o;
+ return mIsInPipMode == other.mIsInPipMode && mOverrideConfig.equals(other.mOverrideConfig);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mIsInPipMode ? 1 : 0);
+ result = 31 * result + mOverrideConfig.hashCode();
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/ResumeActivityItem.java b/android/app/servertransaction/ResumeActivityItem.java
new file mode 100644
index 00000000..ea31a461
--- /dev/null
+++ b/android/app/servertransaction/ResumeActivityItem.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * Request to move an activity to resumed state.
+ * @hide
+ */
+public class ResumeActivityItem extends ActivityLifecycleItem {
+
+ private static final String TAG = "ResumeActivityItem";
+
+ private final int mProcState;
+ private final boolean mIsForward;
+
+ private int mLifecycleSeq;
+
+ public ResumeActivityItem(int procState, boolean isForward) {
+ mProcState = procState;
+ mIsForward = isForward;
+ }
+
+ @Override
+ public void prepare(ClientTransactionHandler client, IBinder token) {
+ mLifecycleSeq = client.getLifecycleSeq();
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Resume transaction for " + client + " received seq: "
+ + mLifecycleSeq);
+ }
+ client.updateProcessState(mProcState, false);
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
+ client.handleResumeActivity(token, true /* clearHide */, mIsForward,
+ true /* reallyResume */, mLifecycleSeq, "RESUME_ACTIVITY");
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return RESUMED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mProcState);
+ dest.writeBoolean(mIsForward);
+ }
+
+ /** Read from Parcel. */
+ private ResumeActivityItem(Parcel in) {
+ mProcState = in.readInt();
+ mIsForward = in.readBoolean();
+ }
+
+ public static final Creator<ResumeActivityItem> CREATOR =
+ new Creator<ResumeActivityItem>() {
+ public ResumeActivityItem createFromParcel(Parcel in) {
+ return new ResumeActivityItem(in);
+ }
+
+ public ResumeActivityItem[] newArray(int size) {
+ return new ResumeActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ResumeActivityItem other = (ResumeActivityItem) o;
+ return mProcState == other.mProcState && mIsForward == other.mIsForward;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mProcState;
+ result = 31 * result + (mIsForward ? 1 : 0);
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/StopActivityItem.java b/android/app/servertransaction/StopActivityItem.java
new file mode 100644
index 00000000..d62c5077
--- /dev/null
+++ b/android/app/servertransaction/StopActivityItem.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * Request to move an activity to stopped state.
+ * @hide
+ */
+public class StopActivityItem extends ActivityLifecycleItem {
+
+ private static final String TAG = "StopActivityItem";
+
+ private final boolean mShowWindow;
+ private final int mConfigChanges;
+
+ private int mLifecycleSeq;
+
+ public StopActivityItem(boolean showWindow, int configChanges) {
+ mShowWindow = showWindow;
+ mConfigChanges = configChanges;
+ }
+
+ @Override
+ public void prepare(ClientTransactionHandler client, IBinder token) {
+ mLifecycleSeq = client.getLifecycleSeq();
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Stop transaction for " + client + " received seq: "
+ + mLifecycleSeq);
+ }
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
+ client.handleStopActivity(token, mShowWindow, mConfigChanges, mLifecycleSeq);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return STOPPED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mShowWindow);
+ dest.writeInt(mConfigChanges);
+ }
+
+ /** Read from Parcel. */
+ private StopActivityItem(Parcel in) {
+ mShowWindow = in.readBoolean();
+ mConfigChanges = in.readInt();
+ }
+
+ public static final Creator<StopActivityItem> CREATOR =
+ new Creator<StopActivityItem>() {
+ public StopActivityItem createFromParcel(Parcel in) {
+ return new StopActivityItem(in);
+ }
+
+ public StopActivityItem[] newArray(int size) {
+ return new StopActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final StopActivityItem other = (StopActivityItem) o;
+ return mShowWindow == other.mShowWindow && mConfigChanges == other.mConfigChanges;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mShowWindow ? 1 : 0);
+ result = 31 * result + mConfigChanges;
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/WindowVisibilityItem.java b/android/app/servertransaction/WindowVisibilityItem.java
new file mode 100644
index 00000000..8e88b38d
--- /dev/null
+++ b/android/app/servertransaction/WindowVisibilityItem.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Window visibility change message.
+ * @hide
+ */
+public class WindowVisibilityItem extends ClientTransactionItem {
+
+ private final boolean mShowWindow;
+
+ public WindowVisibilityItem(boolean showWindow) {
+ mShowWindow = showWindow;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow");
+ client.handleWindowVisibility(token, mShowWindow);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mShowWindow);
+ }
+
+ /** Read from Parcel. */
+ private WindowVisibilityItem(Parcel in) {
+ mShowWindow = in.readBoolean();
+ }
+
+ public static final Creator<WindowVisibilityItem> CREATOR =
+ new Creator<WindowVisibilityItem>() {
+ public WindowVisibilityItem createFromParcel(Parcel in) {
+ return new WindowVisibilityItem(in);
+ }
+
+ public WindowVisibilityItem[] newArray(int size) {
+ return new WindowVisibilityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final WindowVisibilityItem other = (WindowVisibilityItem) o;
+ return mShowWindow == other.mShowWindow;
+ }
+
+ @Override
+ public int hashCode() {
+ return 17 + 31 * (mShowWindow ? 1 : 0);
+ }
+}
diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java
index 616a5be3..ddc5760a 100644
--- a/android/app/slice/Slice.java
+++ b/android/app/slice/Slice.java
@@ -33,7 +33,6 @@ 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;
@@ -41,6 +40,7 @@ import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
/**
* A slice is a piece of app content and actions that can be surfaced outside of the app.
@@ -54,16 +54,25 @@ 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_PARTIAL})
+ HINT_NO_TINT, HINT_PARTIAL})
public @interface SliceHint{ }
/**
+ * The meta-data key that allows an activity to easily be linked directly to a slice.
+ * <p>
+ * An activity can be statically linked to a slice uri by including a meta-data item
+ * for this key that contains a valid slice uri for the same application declaring
+ * the activity.
+ * @hide
+ */
+ public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
+
+ /**
* Hint that this content is a title of other content in the slice. This can also indicate that
* the content should be used in the shortcut representation of the slice (icon, label, action),
* normally this should be indicated by adding the hint on the action containing that content.
*
- * @see SliceView#MODE_SHORTCUT
- * @see SliceItem#TYPE_ACTION
+ * @see SliceItem#FORMAT_ACTION
*/
public static final String HINT_TITLE = "title";
/**
@@ -91,27 +100,13 @@ public final class Slice implements Parcelable {
*/
public static final String HINT_SELECTED = "selected";
/**
- * Hint to indicate that this is a message as part of a communication
- * sequence in this slice.
- */
- public static final String HINT_MESSAGE = "message";
- /**
- * Hint to tag the source (i.e. sender) of a {@link #HINT_MESSAGE}.
- */
- public static final String HINT_SOURCE = "source";
- /**
- * Hint that list items within this slice or subslice would appear better
- * if organized horizontally.
- */
- public static final String HINT_HORIZONTAL = "horizontal";
- /**
* Hint to indicate that this content should not be tinted.
*/
public static final String HINT_NO_TINT = "no_tint";
/**
- * Hint to indicate that this content should not be shown in the {@link SliceView#MODE_SMALL}
- * and {@link SliceView#MODE_LARGE} modes of SliceView. This content may be used to populate
- * the {@link SliceView#MODE_SHORTCUT} format of the slice.
+ * Hint to indicate that this content should not be shown in larger renderings
+ * of Slices. This content may be used to populate the shortcut/icon
+ * format of the slice.
* @hide
*/
public static final String HINT_HIDDEN = "hidden";
@@ -124,32 +119,42 @@ public final class Slice implements Parcelable {
*/
public static final String HINT_TOGGLE = "toggle";
/**
+ * Hint that list items within this slice or subslice would appear better
+ * if organized horizontally.
+ */
+ public static final String HINT_HORIZONTAL = "horizontal";
+ /**
* Hint to indicate that this 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.
- /**
- * @hide
- */
- public static final String HINT_ALT = "alt";
/**
* Key to retrieve an extra added to an intent when a control is changed.
* @hide
*/
public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
+ /**
+ * Subtype to indicate that this is a message as part of a communication
+ * sequence in this slice.
+ */
+ public static final String SUBTYPE_MESSAGE = "message";
+ /**
+ * Subtype to tag the source (i.e. sender) of a {@link #SUBTYPE_MESSAGE}.
+ */
+ public static final String SUBTYPE_SOURCE = "source";
private final SliceItem[] mItems;
private final @SliceHint String[] mHints;
+ private SliceSpec mSpec;
private Uri mUri;
- Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
+ Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri, SliceSpec spec) {
mHints = hints;
mItems = items.toArray(new SliceItem[items.size()]);
mUri = uri;
+ mSpec = spec;
}
protected Slice(Parcel in) {
@@ -160,6 +165,14 @@ public final class Slice implements Parcelable {
mItems[i] = SliceItem.CREATOR.createFromParcel(in);
}
mUri = Uri.CREATOR.createFromParcel(in);
+ mSpec = in.readTypedObject(SliceSpec.CREATOR);
+ }
+
+ /**
+ * @return The spec for this slice
+ */
+ public @Nullable SliceSpec getSpec() {
+ return mSpec;
}
/**
@@ -191,6 +204,7 @@ public final class Slice implements Parcelable {
mItems[i].writeToParcel(dest, flags);
}
mUri.writeToParcel(dest, 0);
+ dest.writeTypedObject(mSpec, flags);
}
@Override
@@ -213,6 +227,7 @@ public final class Slice implements Parcelable {
private final Uri mUri;
private ArrayList<SliceItem> mItems = new ArrayList<>();
private @SliceHint ArrayList<String> mHints = new ArrayList<>();
+ private SliceSpec mSpec;
/**
* Create a builder which will construct a {@link Slice} for the Given Uri.
@@ -248,11 +263,28 @@ public final class Slice implements Parcelable {
}
/**
+ * Add the spec for this slice.
+ */
+ public Builder setSpec(SliceSpec spec) {
+ mSpec = spec;
+ return this;
+ }
+
+ /**
* Add a sub-slice to the slice being constructed
*/
public Builder addSubSlice(@NonNull Slice slice) {
- mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints().toArray(
- new String[slice.getHints().size()])));
+ return addSubSlice(slice, null);
+ }
+
+ /**
+ * Add a sub-slice to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Builder addSubSlice(@NonNull Slice slice, @Nullable String subType) {
+ mItems.add(new SliceItem(slice, SliceItem.FORMAT_SLICE, subType,
+ slice.getHints().toArray(new String[slice.getHints().size()])));
return this;
}
@@ -260,99 +292,132 @@ 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, SliceItem.TYPE_ACTION, new String[0]));
- return this;
+ return addAction(action, s, null);
}
/**
- * Add text to the slice being constructed
+ * Add an action to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addText(CharSequence text, @SliceHint String... hints) {
- mItems.add(new SliceItem(text, SliceItem.TYPE_TEXT, hints));
+ public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s,
+ @Nullable String subType) {
+ List<String> hints = s.getHints();
+ s.mSpec = null;
+ mItems.add(new SliceItem(action, s, SliceItem.FORMAT_ACTION, subType, hints.toArray(
+ new String[hints.size()])));
return this;
}
/**
* Add text to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addText(CharSequence text, @SliceHint List<String> hints) {
- return addText(text, hints.toArray(new String[hints.size()]));
+ public Builder addText(CharSequence text, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(text, SliceItem.FORMAT_TEXT, subType, hints));
+ return this;
}
/**
- * Add an image to the slice being constructed
+ * Add text to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addIcon(Icon icon, @SliceHint String... hints) {
- mItems.add(new SliceItem(icon, SliceItem.TYPE_IMAGE, hints));
- return this;
+ public Builder addText(CharSequence text, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addText(text, subType, hints.toArray(new String[hints.size()]));
}
/**
* Add an image to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addIcon(Icon icon, @SliceHint List<String> hints) {
- return addIcon(icon, hints.toArray(new String[hints.size()]));
+ public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint String... hints) {
+ mItems.add(new SliceItem(icon, SliceItem.FORMAT_IMAGE, subType, hints));
+ return this;
}
/**
- * @hide This isn't final
+ * Add an image to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) {
- mItems.add(new SliceItem(remoteView, SliceItem.TYPE_REMOTE_VIEW, hints));
- return this;
+ public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint List<String> hints) {
+ return addIcon(icon, subType, hints.toArray(new String[hints.size()]));
}
/**
* Add remote input to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addRemoteInput(RemoteInput remoteInput,
+ public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
@SliceHint List<String> hints) {
- return addRemoteInput(remoteInput, hints.toArray(new String[hints.size()]));
+ return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()]));
}
/**
* Add remote input to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addRemoteInput(RemoteInput remoteInput, @SliceHint String... hints) {
- mItems.add(new SliceItem(remoteInput, SliceItem.TYPE_REMOTE_INPUT, hints));
+ public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(remoteInput, SliceItem.FORMAT_REMOTE_INPUT,
+ subType, hints));
return this;
}
/**
* Add a color to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addColor(int color, @SliceHint String... hints) {
- mItems.add(new SliceItem(color, SliceItem.TYPE_COLOR, hints));
+ public Builder addColor(int color, @Nullable String subType, @SliceHint String... hints) {
+ mItems.add(new SliceItem(color, SliceItem.FORMAT_COLOR, subType, hints));
return this;
}
/**
* Add a color to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addColor(int color, @SliceHint List<String> hints) {
- return addColor(color, hints.toArray(new String[hints.size()]));
+ public Builder addColor(int color, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addColor(color, subType, hints.toArray(new String[hints.size()]));
}
/**
* Add a timestamp to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addTimestamp(long time, @SliceHint String... hints) {
- mItems.add(new SliceItem(time, SliceItem.TYPE_TIMESTAMP, hints));
+ public Slice.Builder addTimestamp(long time, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(time, SliceItem.FORMAT_TIMESTAMP, subType,
+ hints));
return this;
}
/**
* Add a timestamp to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addTimestamp(long time, @SliceHint List<String> hints) {
- return addTimestamp(time, hints.toArray(new String[hints.size()]));
+ public Slice.Builder addTimestamp(long time, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addTimestamp(time, subType, hints.toArray(new String[hints.size()]));
}
/**
* Construct the slice.
*/
public Slice build() {
- return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri);
+ return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec);
}
}
@@ -380,15 +445,15 @@ public final class Slice implements Parcelable {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mItems.length; i++) {
sb.append(indent);
- if (mItems[i].getType() == SliceItem.TYPE_SLICE) {
+ if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_SLICE)) {
sb.append("slice:\n");
sb.append(mItems[i].getSlice().toString(indent + " "));
- } else if (mItems[i].getType() == SliceItem.TYPE_TEXT) {
+ } else if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_TEXT)) {
sb.append("text: ");
sb.append(mItems[i].getText());
sb.append("\n");
} else {
- sb.append(SliceItem.typeToString(mItems[i].getType()));
+ sb.append(mItems[i].getFormat());
sb.append("\n");
}
}
@@ -400,10 +465,12 @@ public final class Slice implements Parcelable {
*
* @param resolver ContentResolver to be used.
* @param uri The URI to a slice provider
+ * @param supportedSpecs List of supported specs.
* @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) {
+ public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri,
+ List<SliceSpec> supportedSpecs) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider provider = resolver.acquireProvider(uri);
if (provider == null) {
@@ -412,6 +479,8 @@ public final class Slice implements Parcelable {
try {
Bundle extras = new Bundle();
extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
+ extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
+ new ArrayList<>(supportedSpecs));
final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
null, extras);
Bundle.setDefusable(res, true);
@@ -435,12 +504,14 @@ public final class Slice implements Parcelable {
*
* @param context The context to use.
* @param intent The intent associated with a slice.
+ * @param supportedSpecs List of supported specs.
* @return The Slice provided by the app or null if none is given.
* @see Slice
* @see SliceProvider#onMapIntentToUri(Intent)
* @see Intent
*/
- public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) {
+ public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent,
+ List<SliceSpec> supportedSpecs) {
Preconditions.checkNotNull(intent, "intent");
Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null,
"Slice intent must be explicit " + intent);
@@ -449,7 +520,7 @@ public final class Slice implements Parcelable {
// Check if the intent has data for the slice uri on it and use that
final Uri intentData = intent.getData();
if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) {
- return bindSlice(resolver, intentData);
+ return bindSlice(resolver, intentData, supportedSpecs);
}
// Otherwise ask the app
List<ResolveInfo> providers =
@@ -467,6 +538,8 @@ public final class Slice implements Parcelable {
try {
Bundle extras = new Bundle();
extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
+ extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
+ new ArrayList<>(supportedSpecs));
final Bundle res = provider.call(resolver.getPackageName(),
SliceProvider.METHOD_MAP_INTENT, null, extras);
if (res == null) {
diff --git a/android/app/slice/SliceItem.java b/android/app/slice/SliceItem.java
index 6e69b051..cdeee357 100644
--- a/android/app/slice/SliceItem.java
+++ b/android/app/slice/SliceItem.java
@@ -16,8 +16,8 @@
package android.app.slice;
-import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.StringDef;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.graphics.drawable.Icon;
@@ -38,13 +38,13 @@ import java.util.List;
*
* A SliceItem a piece of content and some hints about what that content
* means or how it should be displayed. The types of content can be:
- * <li>{@link #TYPE_SLICE}</li>
- * <li>{@link #TYPE_TEXT}</li>
- * <li>{@link #TYPE_IMAGE}</li>
- * <li>{@link #TYPE_ACTION}</li>
- * <li>{@link #TYPE_COLOR}</li>
- * <li>{@link #TYPE_TIMESTAMP}</li>
- * <li>{@link #TYPE_REMOTE_INPUT}</li>
+ * <li>{@link #FORMAT_SLICE}</li>
+ * <li>{@link #FORMAT_TEXT}</li>
+ * <li>{@link #FORMAT_IMAGE}</li>
+ * <li>{@link #FORMAT_ACTION}</li>
+ * <li>{@link #FORMAT_COLOR}</li>
+ * <li>{@link #FORMAT_TIMESTAMP}</li>
+ * <li>{@link #FORMAT_REMOTE_INPUT}</li>
*
* The hints that a {@link SliceItem} are a set of strings which annotate
* the content. The hints that are guaranteed to be understood by the system
@@ -55,68 +55,68 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- @IntDef({TYPE_SLICE, TYPE_TEXT, TYPE_IMAGE, TYPE_ACTION, TYPE_COLOR,
- TYPE_TIMESTAMP, TYPE_REMOTE_INPUT})
+ @StringDef({FORMAT_SLICE, FORMAT_TEXT, FORMAT_IMAGE, FORMAT_ACTION, FORMAT_COLOR,
+ FORMAT_TIMESTAMP, FORMAT_REMOTE_INPUT})
public @interface SliceType {}
/**
* A {@link SliceItem} that contains a {@link Slice}
*/
- public static final int TYPE_SLICE = 1;
+ public static final String FORMAT_SLICE = "slice";
/**
* A {@link SliceItem} that contains a {@link CharSequence}
*/
- public static final int TYPE_TEXT = 2;
+ public static final String FORMAT_TEXT = "text";
/**
* A {@link SliceItem} that contains an {@link Icon}
*/
- public static final int TYPE_IMAGE = 3;
+ public static final String FORMAT_IMAGE = "image";
/**
* A {@link SliceItem} that contains a {@link PendingIntent}
*
* Note: Actions contain 2 pieces of data, In addition to the pending intent, the
* item contains a {@link Slice} that the action applies to.
*/
- public static final int TYPE_ACTION = 4;
- /**
- * @hide This isn't final
- */
- public static final int TYPE_REMOTE_VIEW = 5;
+ public static final String FORMAT_ACTION = "action";
/**
* A {@link SliceItem} that contains a Color int.
*/
- public static final int TYPE_COLOR = 6;
+ public static final String FORMAT_COLOR = "color";
/**
* A {@link SliceItem} that contains a timestamp.
*/
- public static final int TYPE_TIMESTAMP = 8;
+ public static final String FORMAT_TIMESTAMP = "timestamp";
/**
* A {@link SliceItem} that contains a {@link RemoteInput}.
*/
- public static final int TYPE_REMOTE_INPUT = 9;
+ public static final String FORMAT_REMOTE_INPUT = "input";
/**
* @hide
*/
protected @Slice.SliceHint
String[] mHints;
- private final int mType;
+ private final String mFormat;
+ private final String mSubType;
private final Object mObj;
/**
* @hide
*/
- public SliceItem(Object obj, @SliceType int type, @Slice.SliceHint String[] hints) {
+ public SliceItem(Object obj, @SliceType String format, String subType,
+ @Slice.SliceHint String[] hints) {
mHints = hints;
- mType = type;
+ mFormat = format;
+ mSubType = subType;
mObj = obj;
}
/**
* @hide
*/
- public SliceItem(PendingIntent intent, Slice slice, int type, @Slice.SliceHint String[] hints) {
- this(new Pair<>(intent, slice), type, hints);
+ public SliceItem(PendingIntent intent, Slice slice, String format, String subType,
+ @Slice.SliceHint String[] hints) {
+ this(new Pair<>(intent, slice), format, subType, hints);
}
/**
@@ -141,26 +141,51 @@ public final class SliceItem implements Parcelable {
ArrayUtils.removeElement(String.class, mHints, hint);
}
- public @SliceType int getType() {
- return mType;
+ /**
+ * Get the format of this SliceItem.
+ * <p>
+ * The format will be one of the following types supported by the platform:
+ * <li>{@link #FORMAT_SLICE}</li>
+ * <li>{@link #FORMAT_TEXT}</li>
+ * <li>{@link #FORMAT_IMAGE}</li>
+ * <li>{@link #FORMAT_ACTION}</li>
+ * <li>{@link #FORMAT_COLOR}</li>
+ * <li>{@link #FORMAT_TIMESTAMP}</li>
+ * <li>{@link #FORMAT_REMOTE_INPUT}</li>
+ * @see #getSubType() ()
+ */
+ public String getFormat() {
+ return mFormat;
+ }
+
+ /**
+ * Get the sub-type of this SliceItem.
+ * <p>
+ * Subtypes provide additional information about the type of this information beyond basic
+ * interpretations inferred by {@link #getFormat()}. For example a slice may contain
+ * many {@link #FORMAT_TEXT} items, but only some of them may be {@link Slice#SUBTYPE_MESSAGE}.
+ * @see #getFormat()
+ */
+ public String getSubType() {
+ return mSubType;
}
/**
- * @return The text held by this {@link #TYPE_TEXT} SliceItem
+ * @return The text held by this {@link #FORMAT_TEXT} SliceItem
*/
public CharSequence getText() {
return (CharSequence) mObj;
}
/**
- * @return The icon held by this {@link #TYPE_IMAGE} SliceItem
+ * @return The icon held by this {@link #FORMAT_IMAGE} SliceItem
*/
public Icon getIcon() {
return (Icon) mObj;
}
/**
- * @return The pending intent held by this {@link #TYPE_ACTION} SliceItem
+ * @return The pending intent held by this {@link #FORMAT_ACTION} SliceItem
*/
public PendingIntent getAction() {
return ((Pair<PendingIntent, Slice>) mObj).first;
@@ -174,31 +199,31 @@ public final class SliceItem implements Parcelable {
}
/**
- * @return The remote input held by this {@link #TYPE_REMOTE_INPUT} SliceItem
+ * @return The remote input held by this {@link #FORMAT_REMOTE_INPUT} SliceItem
*/
public RemoteInput getRemoteInput() {
return (RemoteInput) mObj;
}
/**
- * @return The color held by this {@link #TYPE_COLOR} SliceItem
+ * @return The color held by this {@link #FORMAT_COLOR} SliceItem
*/
public int getColor() {
return (Integer) mObj;
}
/**
- * @return The slice held by this {@link #TYPE_ACTION} or {@link #TYPE_SLICE} SliceItem
+ * @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem
*/
public Slice getSlice() {
- if (getType() == TYPE_ACTION) {
+ if (FORMAT_ACTION.equals(getFormat())) {
return ((Pair<PendingIntent, Slice>) mObj).second;
}
return (Slice) mObj;
}
/**
- * @return The timestamp held by this {@link #TYPE_TIMESTAMP} SliceItem
+ * @return The timestamp held by this {@link #FORMAT_TIMESTAMP} SliceItem
*/
public long getTimestamp() {
return (Long) mObj;
@@ -217,8 +242,9 @@ public final class SliceItem implements Parcelable {
*/
public SliceItem(Parcel in) {
mHints = in.readStringArray();
- mType = in.readInt();
- mObj = readObj(mType, in);
+ mFormat = in.readString();
+ mSubType = in.readString();
+ mObj = readObj(mFormat, in);
}
@Override
@@ -229,8 +255,9 @@ public final class SliceItem implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringArray(mHints);
- dest.writeInt(mType);
- writeObj(dest, flags, mObj, mType);
+ dest.writeString(mFormat);
+ dest.writeString(mSubType);
+ writeObj(dest, flags, mObj, mFormat);
}
/**
@@ -259,49 +286,54 @@ public final class SliceItem implements Parcelable {
return false;
}
- private void writeObj(Parcel dest, int flags, Object obj, int type) {
- switch (type) {
- case TYPE_SLICE:
- case TYPE_REMOTE_VIEW:
- case TYPE_IMAGE:
- case TYPE_REMOTE_INPUT:
+ private static String getBaseType(String type) {
+ int index = type.indexOf('/');
+ if (index >= 0) {
+ return type.substring(0, index);
+ }
+ return type;
+ }
+
+ private static void writeObj(Parcel dest, int flags, Object obj, String type) {
+ switch (getBaseType(type)) {
+ case FORMAT_SLICE:
+ case FORMAT_IMAGE:
+ case FORMAT_REMOTE_INPUT:
((Parcelable) obj).writeToParcel(dest, flags);
break;
- case TYPE_ACTION:
+ case FORMAT_ACTION:
((Pair<PendingIntent, Slice>) obj).first.writeToParcel(dest, flags);
((Pair<PendingIntent, Slice>) obj).second.writeToParcel(dest, flags);
break;
- case TYPE_TEXT:
- TextUtils.writeToParcel((CharSequence) mObj, dest, flags);
+ case FORMAT_TEXT:
+ TextUtils.writeToParcel((CharSequence) obj, dest, flags);
break;
- case TYPE_COLOR:
- dest.writeInt((Integer) mObj);
+ case FORMAT_COLOR:
+ dest.writeInt((Integer) obj);
break;
- case TYPE_TIMESTAMP:
- dest.writeLong((Long) mObj);
+ case FORMAT_TIMESTAMP:
+ dest.writeLong((Long) obj);
break;
}
}
- private static Object readObj(int type, Parcel in) {
- switch (type) {
- case TYPE_SLICE:
+ private static Object readObj(String type, Parcel in) {
+ switch (getBaseType(type)) {
+ case FORMAT_SLICE:
return Slice.CREATOR.createFromParcel(in);
- case TYPE_TEXT:
+ case FORMAT_TEXT:
return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- case TYPE_IMAGE:
+ case FORMAT_IMAGE:
return Icon.CREATOR.createFromParcel(in);
- case TYPE_ACTION:
- return new Pair<PendingIntent, Slice>(
+ case FORMAT_ACTION:
+ return new Pair<>(
PendingIntent.CREATOR.createFromParcel(in),
Slice.CREATOR.createFromParcel(in));
- case TYPE_REMOTE_VIEW:
- return RemoteViews.CREATOR.createFromParcel(in);
- case TYPE_COLOR:
+ case FORMAT_COLOR:
return in.readInt();
- case TYPE_TIMESTAMP:
+ case FORMAT_TIMESTAMP:
return in.readLong();
- case TYPE_REMOTE_INPUT:
+ case FORMAT_REMOTE_INPUT:
return RemoteInput.CREATOR.createFromParcel(in);
}
throw new RuntimeException("Unsupported type " + type);
@@ -318,29 +350,4 @@ public final class SliceItem implements Parcelable {
return new SliceItem[size];
}
};
-
- /**
- * @hide
- */
- public static String typeToString(int type) {
- switch (type) {
- case TYPE_SLICE:
- return "Slice";
- case TYPE_TEXT:
- return "Text";
- case TYPE_IMAGE:
- return "Image";
- case TYPE_ACTION:
- return "Action";
- case TYPE_REMOTE_VIEW:
- return "RemoteView";
- case TYPE_COLOR:
- return "Color";
- case TYPE_TIMESTAMP:
- return "Timestamp";
- case TYPE_REMOTE_INPUT:
- return "RemoteInput";
- }
- return "Unrecognized type: " + type;
- }
}
diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
index 05f4ce6e..ac5365c3 100644
--- a/android/app/slice/SliceProvider.java
+++ b/android/app/slice/SliceProvider.java
@@ -17,7 +17,6 @@ package android.app.slice;
import android.Manifest.permission;
import android.annotation.NonNull;
-import android.app.slice.widget.SliceView;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -37,6 +36,7 @@ import android.os.StrictMode.ThreadPolicy;
import android.os.UserHandle;
import android.util.Log;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
@@ -93,6 +93,10 @@ public abstract class SliceProvider extends ContentProvider {
/**
* @hide
*/
+ public static final String EXTRA_SUPPORTED_SPECS = "supported_specs";
+ /**
+ * @hide
+ */
public static final String METHOD_SLICE = "bind_slice";
/**
* @hide
@@ -118,12 +122,25 @@ public abstract class SliceProvider extends ContentProvider {
* 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>
+ * The slice returned should have a spec that is compatible with one of
+ * the supported specs.
*
+ * @param sliceUri Uri to bind.
+ * @param supportedSpecs List of supported specs.
* @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);
+ public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
+ return onBindSlice(sliceUri);
+ }
+
+ /**
+ * @deprecated migrating to {@link #onBindSlice(Uri, List)}
+ */
+ @Deprecated
+ public Slice onBindSlice(Uri sliceUri) {
+ return null;
+ }
/**
* This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider.
@@ -132,7 +149,6 @@ public abstract class SliceProvider extends ContentProvider {
*
* @return Uri representing the slice associated with the provided intent.
* @see {@link Slice}
- * @see {@link SliceView#setSlice(Intent)}
*/
public @NonNull Uri onMapIntentToUri(Intent intent) {
throw new UnsupportedOperationException(
@@ -195,8 +211,9 @@ public abstract class SliceProvider extends ContentProvider {
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
"Slice binding requires the permission BIND_SLICE");
}
+ List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
- Slice s = handleBindSlice(uri);
+ Slice s = handleBindSlice(uri, supportedSpecs);
Bundle b = new Bundle();
b.putParcelable(EXTRA_SLICE, s);
return b;
@@ -205,9 +222,10 @@ public abstract class SliceProvider extends ContentProvider {
"Slice binding requires the permission BIND_SLICE");
Intent intent = extras.getParcelable(EXTRA_INTENT);
Uri uri = onMapIntentToUri(intent);
+ List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
Bundle b = new Bundle();
if (uri != null) {
- Slice s = handleBindSlice(uri);
+ Slice s = handleBindSlice(uri, supportedSpecs);
b.putParcelable(EXTRA_SLICE, s);
} else {
b.putParcelable(EXTRA_SLICE, null);
@@ -217,14 +235,14 @@ public abstract class SliceProvider extends ContentProvider {
return super.call(method, arg, extras);
}
- private Slice handleBindSlice(Uri sliceUri) {
+ private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
if (Looper.myLooper() == Looper.getMainLooper()) {
- return onBindSliceStrict(sliceUri);
+ return onBindSliceStrict(sliceUri, supportedSpecs);
} else {
CountDownLatch latch = new CountDownLatch(1);
Slice[] output = new Slice[1];
Handler.getMain().post(() -> {
- output[0] = onBindSliceStrict(sliceUri);
+ output[0] = onBindSliceStrict(sliceUri, supportedSpecs);
latch.countDown();
});
try {
@@ -236,14 +254,14 @@ public abstract class SliceProvider extends ContentProvider {
}
}
- private Slice onBindSliceStrict(Uri sliceUri) {
+ private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs) {
ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
try {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyDeath()
.build());
- return onBindSlice(sliceUri);
+ return onBindSlice(sliceUri, supportedSpecs);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
diff --git a/android/app/slice/SliceQuery.java b/android/app/slice/SliceQuery.java
index 9943c492..20eca880 100644
--- a/android/app/slice/SliceQuery.java
+++ b/android/app/slice/SliceQuery.java
@@ -19,6 +19,7 @@ package android.app.slice;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.Queue;
import java.util.Spliterators;
import java.util.stream.Collectors;
@@ -37,14 +38,15 @@ public class SliceQuery {
*/
public static SliceItem getPrimaryIcon(Slice slice) {
for (SliceItem item : slice.getItems()) {
- if (item.getType() == SliceItem.TYPE_IMAGE) {
+ if (Objects.equals(item.getFormat(), SliceItem.FORMAT_IMAGE)) {
return item;
}
- if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+ if (!(compareTypes(item, SliceItem.FORMAT_SLICE)
+ && item.hasHint(Slice.HINT_LIST))
&& !item.hasHint(Slice.HINT_ACTIONS)
&& !item.hasHint(Slice.HINT_LIST_ITEM)
- && (item.getType() != SliceItem.TYPE_ACTION)) {
- SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
+ && !compareTypes(item, SliceItem.FORMAT_ACTION)) {
+ SliceItem icon = SliceQuery.find(item, SliceItem.FORMAT_IMAGE);
if (icon != null) {
return icon;
}
@@ -78,23 +80,23 @@ public class SliceQuery {
/**
* @hide
*/
- public static List<SliceItem> findAll(SliceItem s, int type) {
+ public static List<SliceItem> findAll(SliceItem s, String type) {
return findAll(s, type, (String[]) null, null);
}
/**
* @hide
*/
- public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) {
+ public static List<SliceItem> findAll(SliceItem s, String type, String hints, String nonHints) {
return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
}
/**
* @hide
*/
- public static List<SliceItem> findAll(SliceItem s, int type, String[] hints,
+ public static List<SliceItem> findAll(SliceItem s, String type, String[] hints,
String[] nonHints) {
- return stream(s).filter(item -> (type == -1 || item.getType() == type)
+ return stream(s).filter(item -> compareTypes(item, type)
&& (item.hasHints(hints) && !item.hasAnyHints(nonHints)))
.collect(Collectors.toList());
}
@@ -102,45 +104,45 @@ public class SliceQuery {
/**
* @hide
*/
- public static SliceItem find(Slice s, int type, String hints, String nonHints) {
+ public static SliceItem find(Slice s, String type, String hints, String nonHints) {
return find(s, type, new String[]{ hints }, new String[]{ nonHints });
}
/**
* @hide
*/
- public static SliceItem find(Slice s, int type) {
+ public static SliceItem find(Slice s, String type) {
return find(s, type, (String[]) null, null);
}
/**
* @hide
*/
- public static SliceItem find(SliceItem s, int type) {
+ public static SliceItem find(SliceItem s, String type) {
return find(s, type, (String[]) null, null);
}
/**
* @hide
*/
- public static SliceItem find(SliceItem s, int type, String hints, String nonHints) {
+ public static SliceItem find(SliceItem s, String type, String hints, String nonHints) {
return find(s, type, new String[]{ hints }, new String[]{ nonHints });
}
/**
* @hide
*/
- public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
+ public static SliceItem find(Slice s, String type, String[] hints, String[] nonHints) {
List<String> h = s.getHints();
- return find(new SliceItem(s, SliceItem.TYPE_SLICE, h.toArray(new String[h.size()])), type,
- hints, nonHints);
+ return find(new SliceItem(s, SliceItem.FORMAT_SLICE, null, h.toArray(new String[h.size()])),
+ type, hints, nonHints);
}
/**
* @hide
*/
- public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) {
- return stream(s).filter(item -> (item.getType() == type || type == -1)
+ public static SliceItem find(SliceItem s, String type, String[] hints, String[] nonHints) {
+ return stream(s).filter(item -> compareTypes(item, type)
&& (item.hasHints(hints) && !item.hasAnyHints(nonHints))).findFirst().orElse(null);
}
@@ -159,8 +161,8 @@ public class SliceQuery {
@Override
public SliceItem next() {
SliceItem item = items.poll();
- if (item.getType() == SliceItem.TYPE_SLICE
- || item.getType() == SliceItem.TYPE_ACTION) {
+ if (compareTypes(item, SliceItem.FORMAT_SLICE)
+ || compareTypes(item, SliceItem.FORMAT_ACTION)) {
items.addAll(item.getSlice().getItems());
}
return item;
@@ -168,4 +170,19 @@ public class SliceQuery {
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
}
+
+ /**
+ * @hide
+ */
+ public static boolean compareTypes(SliceItem item, String desiredType) {
+ final int typeLength = desiredType.length();
+ if (typeLength == 3 && desiredType.equals("*/*")) {
+ return true;
+ }
+ if (item.getSubType() == null && desiredType.indexOf('/') < 0) {
+ return item.getFormat().equals(desiredType);
+ }
+ return (item.getFormat() + "/" + item.getSubType())
+ .matches(desiredType.replaceAll("\\*", ".*"));
+ }
}
diff --git a/android/app/slice/SliceSpec.java b/android/app/slice/SliceSpec.java
new file mode 100644
index 00000000..433b67e9
--- /dev/null
+++ b/android/app/slice/SliceSpec.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.slice;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Class describing the structure of the data contained within a slice.
+ * <p>
+ * A data version contains a string which describes the type of structure
+ * and a revision which denotes this specific implementation. Revisions are expected
+ * to be backwards compatible and monotonically increasing. Meaning if a
+ * SliceSpec has the same type and an equal or lesser revision,
+ * it is expected to be compatible.
+ * <p>
+ * Apps rendering slices will provide a list of supported versions to the OS which
+ * will also be given to the app. Apps should only return a {@link Slice} with a
+ * {@link SliceSpec} that one of the supported {@link SliceSpec}s provided
+ * {@link #canRender}.
+ *
+ * @see Slice
+ * @see SliceProvider#onBindSlice(Uri)
+ */
+public final class SliceSpec implements Parcelable {
+
+ private final String mType;
+ private final int mRevision;
+
+ public SliceSpec(@NonNull String type, int revision) {
+ mType = type;
+ mRevision = revision;
+ }
+
+ /**
+ * @hide
+ */
+ public SliceSpec(Parcel source) {
+ mType = source.readString();
+ mRevision = source.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mType);
+ dest.writeInt(mRevision);
+ }
+
+ /**
+ * Gets the type of the version.
+ */
+ public String getType() {
+ return mType;
+ }
+
+ /**
+ * Gets the revision of the version.
+ */
+ public int getRevision() {
+ return mRevision;
+ }
+
+ /**
+ * Indicates that this spec can be used to render the specified spec.
+ * <p>
+ * Rendering support is not bi-directional (e.g. Spec v3 can render
+ * Spec v2, but Spec v2 cannot render Spec v3).
+ *
+ * @param candidate candidate format of data.
+ * @return true if versions are compatible.
+ * @see androidx.app.slice.widget.SliceView
+ */
+ public boolean canRender(@NonNull SliceSpec candidate) {
+ if (!mType.equals(candidate.mType)) return false;
+ return mRevision >= candidate.mRevision;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SliceSpec)) return false;
+ SliceSpec other = (SliceSpec) obj;
+ return mType.equals(other.mType) && mRevision == other.mRevision;
+ }
+
+ public static final Creator<SliceSpec> CREATOR = new Creator<SliceSpec>() {
+ @Override
+ public SliceSpec createFromParcel(Parcel source) {
+ return new SliceSpec(source);
+ }
+
+ @Override
+ public SliceSpec[] newArray(int size) {
+ return new SliceSpec[size];
+ }
+ };
+}
diff --git a/android/app/slice/widget/ActionRow.java b/android/app/slice/widget/ActionRow.java
deleted file mode 100644
index c96e6304..00000000
--- a/android/app/slice/widget/ActionRow.java
+++ /dev/null
@@ -1,201 +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.app.slice.widget;
-
-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.util.TypedValue;
-import android.view.View;
-import android.view.ViewParent;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * @hide
- */
-public class ActionRow extends FrameLayout {
-
- private static final int MAX_ACTIONS = 5;
- private final int mSize;
- private final int mIconPadding;
- private final LinearLayout mActionsGroup;
- private final boolean mFullActions;
- private int mColor = Color.BLACK;
-
- public ActionRow(Context context, boolean fullActions) {
- super(context);
- mFullActions = fullActions;
- mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
- context.getResources().getDisplayMetrics());
- mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
- context.getResources().getDisplayMetrics());
- mActionsGroup = new LinearLayout(context);
- mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
- mActionsGroup.setLayoutParams(
- new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
- addView(mActionsGroup);
- }
-
- private void setColor(int color) {
- mColor = color;
- for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
- View view = mActionsGroup.getChildAt(i);
- SliceItem item = (SliceItem) view.getTag();
- boolean tint = !item.hasHint(Slice.HINT_NO_TINT);
- if (tint) {
- ((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor));
- }
- }
- }
-
- private ImageView addAction(Icon icon, boolean allowTint, SliceItem image) {
- ImageView imageView = new ImageView(getContext());
- imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
- imageView.setScaleType(ScaleType.FIT_CENTER);
- imageView.setImageIcon(icon);
- if (allowTint) {
- imageView.setImageTintList(ColorStateList.valueOf(mColor));
- }
- imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
- android.R.attr.selectableItemBackground));
- imageView.setTag(image);
- addAction(imageView);
- return imageView;
- }
-
- /**
- * Set the actions and color for this action row.
- */
- public void setActions(SliceItem actionRow, SliceItem defColor) {
- removeAllViews();
- mActionsGroup.removeAllViews();
- addView(mActionsGroup);
-
- SliceItem color = SliceQuery.find(actionRow, SliceItem.TYPE_COLOR);
- if (color == null) {
- color = defColor;
- }
- if (color != null) {
- setColor(color.getColor());
- }
- SliceQuery.findAll(actionRow, SliceItem.TYPE_ACTION).forEach(action -> {
- if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
- return;
- }
- SliceItem image = SliceQuery.find(action, SliceItem.TYPE_IMAGE);
- if (image == null) {
- return;
- }
- boolean tint = !image.hasHint(Slice.HINT_NO_TINT);
- SliceItem input = SliceQuery.find(action, SliceItem.TYPE_REMOTE_INPUT);
- if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
- addAction(image.getIcon(), tint, image).setOnClickListener(
- v -> handleRemoteInputClick(v, action.getAction(), input.getRemoteInput()));
- createRemoteInputView(mColor, getContext());
- } else {
- addAction(image.getIcon(), tint, image).setOnClickListener(v -> AsyncTask.execute(
- () -> {
- try {
- action.getAction().send();
- } catch (CanceledException e) {
- e.printStackTrace();
- }
- }));
- }
- });
- setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
- }
-
- private void addAction(View child) {
- mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
- }
-
- private void createRemoteInputView(int color, Context context) {
- View riv = RemoteInputView.inflate(context, this);
- riv.setVisibility(View.INVISIBLE);
- addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- riv.setBackgroundColor(color);
- }
-
- private boolean handleRemoteInputClick(View view, PendingIntent pendingIntent,
- RemoteInput input) {
- if (input == null) {
- return false;
- }
-
- ViewParent p = view.getParent().getParent();
- RemoteInputView riv = null;
- while (p != null) {
- if (p instanceof View) {
- View pv = (View) p;
- riv = findRemoteInputView(pv);
- if (riv != null) {
- break;
- }
- }
- p = p.getParent();
- }
- if (riv == null) {
- return false;
- }
-
- int width = view.getWidth();
- if (view instanceof TextView) {
- // Center the reveal on the text which might be off-center from the TextView
- TextView tv = (TextView) view;
- if (tv.getLayout() != null) {
- int innerWidth = (int) tv.getLayout().getLineWidth(0);
- innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
- width = Math.min(width, innerWidth);
- }
- }
- int cx = view.getLeft() + width / 2;
- int cy = view.getTop() + view.getHeight() / 2;
- int w = riv.getWidth();
- int h = riv.getHeight();
- int r = Math.max(
- Math.max(cx + cy, cx + (h - cy)),
- Math.max((w - cx) + cy, (w - cx) + (h - cy)));
-
- riv.setRevealParameters(cx, cy, r);
- riv.setPendingIntent(pendingIntent);
- riv.setRemoteInput(new RemoteInput[] {
- input
- }, input);
- riv.focusAnimated();
- return true;
- }
-
- private RemoteInputView findRemoteInputView(View v) {
- if (v == null) {
- return null;
- }
- return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
- }
-}
diff --git a/android/app/slice/widget/GridView.java b/android/app/slice/widget/GridView.java
deleted file mode 100644
index 793abc05..00000000
--- a/android/app/slice/widget/GridView.java
+++ /dev/null
@@ -1,192 +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.app.slice.widget;
-
-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.widget.LargeSliceAdapter.SliceListView;
-import android.content.Context;
-import android.graphics.Color;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @hide
- */
-public class GridView extends LinearLayout implements SliceListView {
-
- private static final String TAG = "GridView";
-
- private static final int MAX_IMAGES = 3;
- private static final int MAX_ALL = 5;
- private boolean mIsAllImages;
-
- public GridView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mIsAllImages) {
- int width = MeasureSpec.getSize(widthMeasureSpec);
- int height = width / getChildCount();
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY,
- height);
- getLayoutParams().height = height;
- for (int i = 0; i < getChildCount(); i++) {
- getChildAt(i).getLayoutParams().height = height;
- }
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public void setSliceItem(SliceItem slice) {
- mIsAllImages = true;
- removeAllViews();
- int total = 1;
- if (slice.getType() == SliceItem.TYPE_SLICE) {
- List<SliceItem> items = slice.getSlice().getItems();
- total = items.size();
- for (int i = 0; i < total; i++) {
- SliceItem item = items.get(i);
- if (isFull()) {
- continue;
- }
- if (!addItem(item)) {
- mIsAllImages = false;
- }
- }
- } else {
- if (!isFull()) {
- if (!addItem(slice)) {
- mIsAllImages = false;
- }
- }
- }
- if (total > getChildCount() && mIsAllImages) {
- addExtraCount(total - getChildCount());
- }
- }
-
- private void addExtraCount(int numExtra) {
- View last = getChildAt(getChildCount() - 1);
- FrameLayout frame = new FrameLayout(getContext());
- frame.setLayoutParams(last.getLayoutParams());
-
- removeView(last);
- frame.addView(last, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
- TextView v = new TextView(getContext());
- v.setTextColor(Color.WHITE);
- v.setBackgroundColor(0x4d000000);
- v.setText(getResources().getString(R.string.slice_more_content, numExtra));
- v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
- v.setGravity(Gravity.CENTER);
- frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
- addView(frame);
- }
-
- private boolean isFull() {
- return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
- }
-
- /**
- * Returns true if this item is just an image.
- */
- private boolean addItem(SliceItem item) {
- if (item.hasHint(Slice.HINT_HIDDEN)) {
- return false;
- }
- if (item.getType() == SliceItem.TYPE_IMAGE) {
- ImageView v = new ImageView(getContext());
- v.setImageIcon(item.getIcon());
- v.setScaleType(ScaleType.CENTER_CROP);
- addView(v, new LayoutParams(0, MATCH_PARENT, 1));
- return true;
- } else {
- LinearLayout v = new LinearLayout(getContext());
- int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- 12, getContext().getResources().getDisplayMetrics());
- v.setPadding(0, s, 0, 0);
- v.setOrientation(LinearLayout.VERTICAL);
- v.setGravity(Gravity.CENTER_HORIZONTAL);
- // TODO: Unify sporadic inflates that happen throughout the code.
- ArrayList<SliceItem> items = new ArrayList<>();
- if (item.getType() == SliceItem.TYPE_SLICE) {
- items.addAll(item.getSlice().getItems());
- }
- items.forEach(i -> {
- if (i.hasHint(Slice.HINT_HIDDEN)) {
- return;
- }
- Context context = getContext();
- switch (i.getType()) {
- case SliceItem.TYPE_TEXT:
- boolean title = false;
- if ((item.hasAnyHints(new String[] {
- Slice.HINT_LARGE, Slice.HINT_TITLE
- }))) {
- title = true;
- }
- TextView tv = (TextView) LayoutInflater.from(context).inflate(
- title ? R.layout.slice_title : R.layout.slice_secondary_text, null);
- tv.setText(i.getText());
- v.addView(tv);
- break;
- case SliceItem.TYPE_IMAGE:
- ImageView iv = new ImageView(context);
- iv.setImageIcon(i.getIcon());
- if (item.hasHint(Slice.HINT_LARGE)) {
- iv.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- } else {
- int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- 48, context.getResources().getDisplayMetrics());
- iv.setLayoutParams(new LayoutParams(size, size));
- }
- v.addView(iv);
- break;
- case SliceItem.TYPE_REMOTE_VIEW:
- v.addView(i.getRemoteView().apply(context, v));
- break;
- case SliceItem.TYPE_COLOR:
- // TODO: Support color to tint stuff here.
- break;
- }
- });
- addView(v, new LayoutParams(0, WRAP_CONTENT, 1));
- return false;
- }
- }
-}
diff --git a/android/app/slice/widget/LargeSliceAdapter.java b/android/app/slice/widget/LargeSliceAdapter.java
deleted file mode 100644
index 267fff6a..00000000
--- a/android/app/slice/widget/LargeSliceAdapter.java
+++ /dev/null
@@ -1,224 +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.app.slice.widget;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.widget.LargeSliceAdapter.SliceViewHolder;
-import android.content.Context;
-import android.util.ArrayMap;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.FrameLayout;
-
-import com.android.internal.R;
-import com.android.internal.widget.RecyclerView;
-import com.android.internal.widget.RecyclerView.ViewHolder;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * @hide
- */
-public class LargeSliceAdapter extends RecyclerView.Adapter<SliceViewHolder> {
-
- public static final int TYPE_DEFAULT = 1;
- public static final int TYPE_HEADER = 2;
- public static final int TYPE_GRID = 3;
- public static final int TYPE_MESSAGE = 4;
- public static final int TYPE_MESSAGE_LOCAL = 5;
- public static final int TYPE_REMOTE_VIEWS = 6;
-
- private final IdGenerator mIdGen = new IdGenerator();
- private final Context mContext;
- private List<SliceWrapper> mSlices = new ArrayList<>();
- private SliceItem mColor;
-
- public LargeSliceAdapter(Context context) {
- mContext = context;
- setHasStableIds(true);
- }
-
- /**
- * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color.
- */
- public void setSliceItems(List<SliceItem> slices, SliceItem color) {
- mColor = color;
- mIdGen.resetUsage();
- mSlices = slices.stream().map(s -> new SliceWrapper(s, mIdGen))
- .collect(Collectors.toList());
- notifyDataSetChanged();
- }
-
- @Override
- public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View v = inflateForType(viewType);
- v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
- return new SliceViewHolder(v);
- }
-
- @Override
- public int getItemViewType(int position) {
- return mSlices.get(position).mType;
- }
-
- @Override
- public long getItemId(int position) {
- return mSlices.get(position).mId;
- }
-
- @Override
- public int getItemCount() {
- return mSlices.size();
- }
-
- @Override
- public void onBindViewHolder(SliceViewHolder holder, int position) {
- SliceWrapper slice = mSlices.get(position);
- if (holder.mSliceView != null) {
- holder.mSliceView.setColor(mColor);
- holder.mSliceView.setSliceItem(slice.mItem);
- } else if (slice.mType == TYPE_REMOTE_VIEWS) {
- FrameLayout frame = (FrameLayout) holder.itemView;
- frame.removeAllViews();
- frame.addView(slice.mItem.getRemoteView().apply(mContext, frame));
- }
- }
-
- private View inflateForType(int viewType) {
- switch (viewType) {
- case TYPE_REMOTE_VIEWS:
- return new FrameLayout(mContext);
- case TYPE_GRID:
- return LayoutInflater.from(mContext).inflate(R.layout.slice_grid, null);
- case TYPE_MESSAGE:
- return LayoutInflater.from(mContext).inflate(R.layout.slice_message, null);
- case TYPE_MESSAGE_LOCAL:
- return LayoutInflater.from(mContext).inflate(R.layout.slice_message_local, null);
- }
- return new SmallTemplateView(mContext);
- }
-
- protected static class SliceWrapper {
- private final SliceItem mItem;
- private final int mType;
- private final long mId;
-
- public SliceWrapper(SliceItem item, IdGenerator idGen) {
- mItem = item;
- mType = getType(item);
- mId = idGen.getId(item);
- }
-
- public static int getType(SliceItem item) {
- if (item.getType() == SliceItem.TYPE_REMOTE_VIEW) {
- return TYPE_REMOTE_VIEWS;
- }
- if (item.hasHint(Slice.HINT_MESSAGE)) {
- // TODO: Better way to determine me or not? Something more like Messaging style.
- if (SliceQuery.find(item, -1, Slice.HINT_SOURCE, null) != null) {
- return TYPE_MESSAGE;
- } else {
- return TYPE_MESSAGE_LOCAL;
- }
- }
- if (item.hasHint(Slice.HINT_HORIZONTAL)) {
- return TYPE_GRID;
- }
- return TYPE_DEFAULT;
- }
- }
-
- /**
- * A {@link ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
- */
- public static class SliceViewHolder extends ViewHolder {
- public final SliceListView mSliceView;
-
- public SliceViewHolder(View itemView) {
- super(itemView);
- mSliceView = itemView instanceof SliceListView ? (SliceListView) itemView : null;
- }
- }
-
- /**
- * View slices being displayed in {@link LargeSliceAdapter}.
- */
- public interface SliceListView {
- /**
- * Set the slice item for this view.
- */
- void setSliceItem(SliceItem slice);
-
- /**
- * Set the color for the items in this view.
- */
- default void setColor(SliceItem color) {
-
- }
- }
-
- private static class IdGenerator {
- private long mNextLong = 0;
- private final ArrayMap<String, Long> mCurrentIds = new ArrayMap<>();
- private final ArrayMap<String, Integer> mUsedIds = new ArrayMap<>();
-
- public long getId(SliceItem item) {
- String str = genString(item);
- if (!mCurrentIds.containsKey(str)) {
- mCurrentIds.put(str, mNextLong++);
- }
- long id = mCurrentIds.get(str);
- int index = mUsedIds.getOrDefault(str, 0);
- mUsedIds.put(str, index + 1);
- return id + index * 10000;
- }
-
- private String genString(SliceItem item) {
- StringBuilder builder = new StringBuilder();
- SliceQuery.stream(item).forEach(i -> {
- builder.append(i.getType());
- i.removeHint(Slice.HINT_SELECTED);
- builder.append(i.getHints());
- switch (i.getType()) {
- case SliceItem.TYPE_REMOTE_VIEW:
- builder.append(i.getRemoteView());
- break;
- case SliceItem.TYPE_IMAGE:
- builder.append(i.getIcon());
- break;
- case SliceItem.TYPE_TEXT:
- builder.append(i.getText());
- break;
- case SliceItem.TYPE_COLOR:
- builder.append(i.getColor());
- break;
- }
- });
- return builder.toString();
- }
-
- public void resetUsage() {
- mUsedIds.clear();
- }
- }
-}
diff --git a/android/app/slice/widget/LargeTemplateView.java b/android/app/slice/widget/LargeTemplateView.java
deleted file mode 100644
index 788f6fb6..00000000
--- a/android/app/slice/widget/LargeTemplateView.java
+++ /dev/null
@@ -1,131 +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.app.slice.widget;
-
-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.widget.SliceView.SliceModeView;
-import android.content.Context;
-import android.util.TypedValue;
-
-import com.android.internal.widget.LinearLayoutManager;
-import com.android.internal.widget.RecyclerView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @hide
- */
-public class LargeTemplateView extends SliceModeView {
-
- private final LargeSliceAdapter mAdapter;
- private final RecyclerView mRecyclerView;
- private final int mDefaultHeight;
- private final int mMaxHeight;
- private Slice mSlice;
- private boolean mIsScrollable;
-
- public LargeTemplateView(Context context) {
- super(context);
-
- mRecyclerView = new RecyclerView(getContext());
- mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- mAdapter = new LargeSliceAdapter(context);
- mRecyclerView.setAdapter(mAdapter);
- addView(mRecyclerView);
- mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
- getResources().getDisplayMetrics());
- mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
- getResources().getDisplayMetrics());
- }
-
- @Override
- public String getMode() {
- return SliceView.MODE_LARGE;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mRecyclerView.getMeasuredHeight() > mMaxHeight
- || (mSlice != null && mSlice.hasHint(Slice.HINT_PARTIAL))) {
- mRecyclerView.getLayoutParams().height = mDefaultHeight;
- } else {
- mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public void setSlice(Slice slice) {
- SliceItem color = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
- mSlice = slice;
- List<SliceItem> items = new ArrayList<>();
- boolean[] hasHeader = new boolean[1];
- if (slice.hasHint(Slice.HINT_LIST)) {
- addList(slice, items);
- } else {
- slice.getItems().forEach(item -> {
- if (item.hasHint(Slice.HINT_HIDDEN)) {
- // If it's hidden we don't show it
- return;
- } else if (item.hasHint(Slice.HINT_ACTIONS)) {
- // Action groups don't show in lists
- return;
- } else if (item.getType() == SliceItem.TYPE_COLOR) {
- // A color is not a list item
- return;
- } else if (item.getType() == SliceItem.TYPE_SLICE
- && item.hasHint(Slice.HINT_LIST)) {
- addList(item.getSlice(), items);
- } else if (item.hasHint(Slice.HINT_LIST_ITEM)) {
- items.add(item);
- } else if (!hasHeader[0]) {
- hasHeader[0] = true;
- items.add(0, item);
- } else {
- item.addHint(Slice.HINT_LIST_ITEM);
- items.add(item);
- }
- });
- }
- mAdapter.setSliceItems(items, color);
- }
-
- private void addList(Slice slice, List<SliceItem> items) {
- List<SliceItem> sliceItems = slice.getItems();
- sliceItems.forEach(i -> {
- if (!i.hasHint(Slice.HINT_HIDDEN) && i.getType() != SliceItem.TYPE_COLOR) {
- i.addHint(Slice.HINT_LIST_ITEM);
- items.add(i);
- }
- });
- }
-
- /**
- * Whether or not the content in this template should be scrollable.
- */
- public void setScrollable(boolean isScrollable) {
- // TODO -- restrict / enable how much this view can show
- mIsScrollable = isScrollable;
- }
-}
diff --git a/android/app/slice/widget/MessageView.java b/android/app/slice/widget/MessageView.java
deleted file mode 100644
index 3124398e..00000000
--- a/android/app/slice/widget/MessageView.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.app.slice.widget;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.widget.LargeSliceAdapter.SliceListView;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.text.SpannableStringBuilder;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * @hide
- */
-public class MessageView extends LinearLayout implements SliceListView {
-
- private TextView mDetails;
- private ImageView mIcon;
-
- public MessageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mDetails = findViewById(android.R.id.summary);
- mIcon = findViewById(android.R.id.icon);
- }
-
- @Override
- public void setSliceItem(SliceItem slice) {
- SliceItem source = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_SOURCE, null);
- if (source != null) {
- final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- 24, getContext().getResources().getDisplayMetrics());
- // TODO try and turn this into a drawable
- Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
- Canvas iconCanvas = new Canvas(iconBm);
- Drawable d = source.getIcon().loadDrawable(getContext());
- d.setBounds(0, 0, iconSize, iconSize);
- d.draw(iconCanvas);
- mIcon.setImageBitmap(SliceViewUtil.getCircularBitmap(iconBm));
- }
- SpannableStringBuilder builder = new SpannableStringBuilder();
- SliceQuery.findAll(slice, SliceItem.TYPE_TEXT).forEach(text -> {
- if (builder.length() != 0) {
- builder.append('\n');
- }
- builder.append(text.getText());
- });
- mDetails.setText(builder.toString());
- }
-
-}
diff --git a/android/app/slice/widget/RemoteInputView.java b/android/app/slice/widget/RemoteInputView.java
deleted file mode 100644
index 6eff5afb..00000000
--- a/android/app/slice/widget/RemoteInputView.java
+++ /dev/null
@@ -1,445 +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.app.slice.widget;
-
-import android.animation.Animator;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.RemoteInput;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutManager;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.internal.R;
-
-/**
- * Host for the remote input.
- *
- * @hide
- */
-// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
-public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
-
- private static final String TAG = "RemoteInput";
-
- /**
- * A marker object that let's us easily find views of this class.
- */
- public static final Object VIEW_TAG = new Object();
-
- private RemoteEditText mEditText;
- private ImageButton mSendButton;
- private ProgressBar mProgressBar;
- private PendingIntent mPendingIntent;
- private RemoteInput[] mRemoteInputs;
- private RemoteInput mRemoteInput;
-
- private int mRevealCx;
- private int mRevealCy;
- private int mRevealR;
- private boolean mResetting;
-
- public RemoteInputView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mProgressBar = findViewById(R.id.remote_input_progress);
- mSendButton = findViewById(R.id.remote_input_send);
- mSendButton.setOnClickListener(this);
-
- mEditText = (RemoteEditText) getChildAt(0);
- mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
- @Override
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- final boolean isSoftImeEvent = event == null
- && (actionId == EditorInfo.IME_ACTION_DONE
- || actionId == EditorInfo.IME_ACTION_NEXT
- || actionId == EditorInfo.IME_ACTION_SEND);
- final boolean isKeyboardEnterKey = event != null
- && KeyEvent.isConfirmKey(event.getKeyCode())
- && event.getAction() == KeyEvent.ACTION_DOWN;
-
- if (isSoftImeEvent || isKeyboardEnterKey) {
- if (mEditText.length() > 0) {
- sendRemoteInput();
- }
- // Consume action to prevent IME from closing.
- return true;
- }
- return false;
- }
- });
- mEditText.addTextChangedListener(this);
- mEditText.setInnerFocusable(false);
- mEditText.mRemoteInputView = this;
- }
-
- private void sendRemoteInput() {
- Bundle results = new Bundle();
- results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
- Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
- results);
-
- mEditText.setEnabled(false);
- mSendButton.setVisibility(INVISIBLE);
- mProgressBar.setVisibility(VISIBLE);
- mEditText.mShowImeOnInputConnection = false;
-
- // Tell ShortcutManager that this package has been "activated". ShortcutManager
- // will reset the throttling for this package.
- // Strictly speaking, the intent receiver may be different from the intent creator,
- // but that's an edge case, and also because we can't always know which package will receive
- // an intent, so we just reset for the creator.
- getContext().getSystemService(ShortcutManager.class).onApplicationActive(
- mPendingIntent.getCreatorPackage(),
- getContext().getUserId());
-
- try {
- mPendingIntent.send(mContext, 0, fillInIntent);
- reset();
- } catch (PendingIntent.CanceledException e) {
- Log.i(TAG, "Unable to send remote input result", e);
- Toast.makeText(mContext, "Failure sending pending intent for inline reply :(",
- Toast.LENGTH_SHORT).show();
- reset();
- }
- }
-
- /**
- * Creates a remote input view.
- */
- public static RemoteInputView inflate(Context context, ViewGroup root) {
- RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(
- R.layout.slice_remote_input, root, false);
- v.setTag(VIEW_TAG);
- return v;
- }
-
- @Override
- public void onClick(View v) {
- if (v == mSendButton) {
- sendRemoteInput();
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- super.onTouchEvent(event);
-
- // We never want for a touch to escape to an outer view or one we covered.
- return true;
- }
-
- private void onDefocus() {
- setVisibility(INVISIBLE);
- }
-
- /**
- * Set the pending intent for remote input.
- */
- public void setPendingIntent(PendingIntent pendingIntent) {
- mPendingIntent = pendingIntent;
- }
-
- /**
- * Set the remote inputs for this view.
- */
- public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
- mRemoteInputs = remoteInputs;
- mRemoteInput = remoteInput;
- mEditText.setHint(mRemoteInput.getLabel());
- }
-
- /**
- * Focuses the remote input view.
- */
- public void focusAnimated() {
- if (getVisibility() != VISIBLE) {
- Animator animator = ViewAnimationUtils.createCircularReveal(
- this, mRevealCx, mRevealCy, 0, mRevealR);
- animator.setDuration(200);
- animator.start();
- }
- focus();
- }
-
- private void focus() {
- setVisibility(VISIBLE);
- mEditText.setInnerFocusable(true);
- mEditText.mShowImeOnInputConnection = true;
- mEditText.setSelection(mEditText.getText().length());
- mEditText.requestFocus();
- updateSendButton();
- }
-
- private void reset() {
- mResetting = true;
-
- mEditText.getText().clear();
- mEditText.setEnabled(true);
- mSendButton.setVisibility(VISIBLE);
- mProgressBar.setVisibility(INVISIBLE);
- updateSendButton();
- onDefocus();
-
- mResetting = false;
- }
-
- @Override
- public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
- if (mResetting && child == mEditText) {
- // Suppress text events if it happens during resetting. Ideally this would be
- // suppressed by the text view not being shown, but that doesn't work here because it
- // needs to stay visible for the animation.
- return false;
- }
- return super.onRequestSendAccessibilityEvent(child, event);
- }
-
- private void updateSendButton() {
- mSendButton.setEnabled(mEditText.getText().length() != 0);
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- updateSendButton();
- }
-
- /**
- * Tries to find an action that matches the current pending intent of this view and updates its
- * state to that of the found action
- *
- * @return true if a matching action was found, false otherwise
- */
- public boolean updatePendingIntentFromActions(Notification.Action[] actions) {
- if (mPendingIntent == null || actions == null) {
- return false;
- }
- Intent current = mPendingIntent.getIntent();
- if (current == null) {
- return false;
- }
-
- for (Notification.Action a : actions) {
- RemoteInput[] inputs = a.getRemoteInputs();
- if (a.actionIntent == null || inputs == null) {
- continue;
- }
- Intent candidate = a.actionIntent.getIntent();
- if (!current.filterEquals(candidate)) {
- continue;
- }
-
- RemoteInput input = null;
- for (RemoteInput i : inputs) {
- if (i.getAllowFreeFormInput()) {
- input = i;
- }
- }
- if (input == null) {
- continue;
- }
- setPendingIntent(a.actionIntent);
- setRemoteInput(inputs, input);
- return true;
- }
- return false;
- }
-
- /**
- * @hide
- */
- public void setRevealParameters(int cx, int cy, int r) {
- mRevealCx = cx;
- mRevealCy = cy;
- mRevealR = r;
- }
-
- @Override
- public void dispatchStartTemporaryDetach() {
- super.dispatchStartTemporaryDetach();
- // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
- // won't lose IME focus.
- detachViewFromParent(mEditText);
- }
-
- @Override
- public void dispatchFinishTemporaryDetach() {
- if (isAttachedToWindow()) {
- attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
- } else {
- removeDetachedView(mEditText, false /* animate */);
- }
- super.dispatchFinishTemporaryDetach();
- }
-
- /**
- * An EditText that changes appearance based on whether it's focusable and becomes un-focusable
- * whenever the user navigates away from it or it becomes invisible.
- */
- public static class RemoteEditText extends EditText {
-
- private final Drawable mBackground;
- private RemoteInputView mRemoteInputView;
- boolean mShowImeOnInputConnection;
-
- public RemoteEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- mBackground = getBackground();
- }
-
- private void defocusIfNeeded(boolean animate) {
- if (mRemoteInputView != null || isTemporarilyDetached()) {
- if (isTemporarilyDetached()) {
- // We might get reattached but then the other one of HUN / expanded might steal
- // our focus, so we'll need to save our text here.
- }
- return;
- }
- if (isFocusable() && isEnabled()) {
- setInnerFocusable(false);
- if (mRemoteInputView != null) {
- mRemoteInputView.onDefocus();
- }
- mShowImeOnInputConnection = false;
- }
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
-
- if (!isShown()) {
- defocusIfNeeded(false /* animate */);
- }
- }
-
- @Override
- protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(focused, direction, previouslyFocusedRect);
- if (!focused) {
- defocusIfNeeded(true /* animate */);
- }
- }
-
- @Override
- public void getFocusedRect(Rect r) {
- super.getFocusedRect(r);
- r.top = mScrollY;
- r.bottom = mScrollY + (mBottom - mTop);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- // Eat the DOWN event here to prevent any default behavior.
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- defocusIfNeeded(true /* animate */);
- return true;
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
-
- if (mShowImeOnInputConnection && inputConnection != null) {
- final InputMethodManager imm = InputMethodManager.getInstance();
- if (imm != null) {
- // onCreateInputConnection is called by InputMethodManager in the middle of
- // setting up the connection to the IME; wait with requesting the IME until that
- // work has completed.
- post(new Runnable() {
- @Override
- public void run() {
- imm.viewClicked(RemoteEditText.this);
- imm.showSoftInput(RemoteEditText.this, 0);
- }
- });
- }
- }
-
- return inputConnection;
- }
-
- @Override
- public void onCommitCompletion(CompletionInfo text) {
- clearComposingText();
- setText(text.getText());
- setSelection(getText().length());
- }
-
- void setInnerFocusable(boolean focusable) {
- setFocusableInTouchMode(focusable);
- setFocusable(focusable);
- setCursorVisible(focusable);
-
- if (focusable) {
- requestFocus();
- setBackground(mBackground);
- } else {
- setBackground(null);
- }
-
- }
- }
-}
diff --git a/android/app/slice/widget/ShortcutView.java b/android/app/slice/widget/ShortcutView.java
deleted file mode 100644
index 0b7ad0d6..00000000
--- a/android/app/slice/widget/ShortcutView.java
+++ /dev/null
@@ -1,175 +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.app.slice.widget;
-
-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.widget.SliceView.SliceModeView;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.OvalShape;
-import android.net.Uri;
-
-import com.android.internal.R;
-
-import java.util.List;
-
-/**
- * @hide
- */
-public class ShortcutView extends SliceModeView {
-
- private static final String TAG = "ShortcutView";
-
- private Uri mUri;
- private PendingIntent mAction;
- private SliceItem mLabel;
- private SliceItem mIcon;
-
- private int mLargeIconSize;
- private int mSmallIconSize;
-
- public ShortcutView(Context context) {
- super(context);
- final Resources res = getResources();
- mSmallIconSize = res.getDimensionPixelSize(R.dimen.slice_icon_size);
- mLargeIconSize = res.getDimensionPixelSize(R.dimen.slice_shortcut_size);
- }
-
- @Override
- public void setSlice(Slice slice) {
- removeAllViews();
- determineShortcutItems(getContext(), slice);
- SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
- if (colorItem == null) {
- colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
- }
- // TODO: pick better default colour
- final int color = colorItem != null ? colorItem.getColor() : Color.GRAY;
- ShapeDrawable circle = new ShapeDrawable(new OvalShape());
- circle.setTint(color);
- setBackground(circle);
- if (mIcon != null) {
- final boolean isLarge = mIcon.hasHint(Slice.HINT_LARGE);
- final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
- SliceViewUtil.createCircledIcon(getContext(), color, iconSize, mIcon.getIcon(),
- isLarge, this /* parent */);
- mUri = slice.getUri();
- setClickable(true);
- } else {
- setClickable(false);
- }
- }
-
- @Override
- public String getMode() {
- return SliceView.MODE_SHORTCUT;
- }
-
- @Override
- public boolean performClick() {
- if (!callOnClick()) {
- try {
- if (mAction != null) {
- mAction.send();
- } else {
- Intent intent = new Intent(Intent.ACTION_VIEW).setData(mUri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- getContext().startActivity(intent);
- }
- } catch (CanceledException e) {
- e.printStackTrace();
- }
- }
- return true;
- }
-
- /**
- * Looks at the slice and determines which items are best to use to compose the shortcut.
- */
- private void determineShortcutItems(Context context, Slice slice) {
- List<String> h = slice.getHints();
- SliceItem sliceItem = new SliceItem(slice, SliceItem.TYPE_SLICE,
- h.toArray(new String[h.size()]));
- SliceItem titleItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION,
- Slice.HINT_TITLE, null);
-
- if (titleItem != null) {
- // Preferred case: hinted action containing hinted image and text
- mAction = titleItem.getAction();
- mIcon = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
- null);
- mLabel = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
- null);
- } else {
- // No hinted action; just use the first one
- SliceItem actionItem = SliceQuery.find(sliceItem, SliceItem.TYPE_ACTION, (String) null,
- null);
- mAction = (actionItem != null) ? actionItem.getAction() : null;
- }
- // First fallback: any hinted image and text
- if (mIcon == null) {
- mIcon = SliceQuery.find(sliceItem, SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
- null);
- }
- if (mLabel == null) {
- mLabel = SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
- null);
- }
- // Second fallback: first image and text
- if (mIcon == null) {
- mIcon = SliceQuery.find(sliceItem, SliceItem.TYPE_IMAGE, (String) null,
- null);
- }
- if (mLabel == null) {
- mLabel = SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT, (String) null,
- null);
- }
- // Final fallback: use app info
- if (mIcon == null || mLabel == null || mAction == null) {
- PackageManager pm = context.getPackageManager();
- ProviderInfo providerInfo = pm.resolveContentProvider(
- slice.getUri().getAuthority(), 0);
- ApplicationInfo appInfo = providerInfo.applicationInfo;
- if (appInfo != null) {
- if (mIcon == null) {
- Drawable icon = appInfo.loadDefaultIcon(pm);
- mIcon = new SliceItem(SliceViewUtil.createIconFromDrawable(icon),
- SliceItem.TYPE_IMAGE, new String[] {Slice.HINT_LARGE});
- }
- if (mLabel == null) {
- mLabel = new SliceItem(pm.getApplicationLabel(appInfo),
- SliceItem.TYPE_TEXT, null);
- }
- if (mAction == null) {
- mAction = PendingIntent.getActivity(context, 0,
- pm.getLaunchIntentForPackage(appInfo.packageName), 0);
- }
- }
- }
- }
-}
diff --git a/android/app/slice/widget/SliceView.java b/android/app/slice/widget/SliceView.java
deleted file mode 100644
index fa1b64ce..00000000
--- a/android/app/slice/widget/SliceView.java
+++ /dev/null
@@ -1,422 +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.app.slice.widget;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-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.database.ContentObserver;
-import android.graphics.drawable.ColorDrawable;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import com.android.internal.R;
-import com.android.internal.util.Preconditions;
-
-import java.util.List;
-
-/**
- * A view for displaying a {@link Slice} which is a piece of app content and actions. SliceView is
- * able to present slice content in a templated format outside of the associated app. The way this
- * content is displayed depends on the structure of the slice, the hints associated with the
- * content, and the mode that SliceView is configured for. The modes that SliceView supports are:
- * <ul>
- * <li><b>Shortcut</b>: A shortcut is presented as an icon and a text label representing the main
- * content or action associated with the slice.</li>
- * <li><b>Small</b>: The small format has a restricted height and can present a single
- * {@link SliceItem} or a limited collection of items.</li>
- * <li><b>Large</b>: The large format displays multiple small templates in a list, if scrolling is
- * not enabled (see {@link #setScrollable(boolean)}) the view will show as many items as it can
- * comfortably fit.</li>
- * </ul>
- * <p>
- * When constructing a slice, the contents of it can be annotated with hints, these provide the OS
- * with some information on how the content should be displayed. For example, text annotated with
- * {@link Slice#HINT_TITLE} would be placed in the title position of a template. A slice annotated
- * with {@link Slice#HINT_LIST} would present the child items of that slice in a list.
- * <p>
- * SliceView can be provided a slice via a uri {@link #setSlice(Uri)} in which case a content
- * observer will be set for that uri and the view will update if there are any changes to the slice.
- * To use this the app must have a special permission to bind to the slice (see
- * {@link android.Manifest.permission#BIND_SLICE}).
- * <p>
- * Example usage:
- *
- * <pre class="prettyprint">
- * SliceView v = new SliceView(getContext());
- * v.setMode(desiredMode);
- * v.setSlice(sliceUri);
- * </pre>
- */
-public class SliceView extends ViewGroup {
-
- private static final String TAG = "SliceView";
-
- /**
- * @hide
- */
- public abstract static class SliceModeView extends FrameLayout {
-
- public SliceModeView(Context context) {
- super(context);
- }
-
- /**
- * @return the mode of the slice being presented.
- */
- public abstract String getMode();
-
- /**
- * @param slice the slice to show in this view.
- */
- public abstract void setSlice(Slice slice);
- }
-
- /**
- * @hide
- */
- @StringDef({
- MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
- })
- public @interface SliceMode {}
-
- /**
- * Mode indicating this slice should be presented in small template format.
- */
- public static final String MODE_SMALL = "SLICE_SMALL";
- /**
- * Mode indicating this slice should be presented in large template format.
- */
- public static final String MODE_LARGE = "SLICE_LARGE";
- /**
- * Mode indicating this slice should be presented as an icon. A shortcut requires an intent,
- * icon, and label. This can be indicated by using {@link Slice#HINT_TITLE} on an action in a
- * slice.
- */
- public static final String MODE_SHORTCUT = "SLICE_ICON";
-
- /**
- * Will select the type of slice binding based on size of the View. TODO: Put in some info about
- * that selection.
- */
- private static final String MODE_AUTO = "auto";
-
- private String mMode = MODE_AUTO;
- private SliceModeView mCurrentView;
- private final ActionRow mActions;
- private Slice mCurrentSlice;
- private boolean mShowActions = true;
- private boolean mIsScrollable;
- private SliceObserver mObserver;
- private final int mShortcutSize;
-
- public SliceView(Context context) {
- this(context, null);
- }
-
- public SliceView(Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SliceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
- mActions = new ActionRow(mContext, true);
- mActions.setBackground(new ColorDrawable(0xffeeeeee));
- mCurrentView = new LargeTemplateView(mContext);
- addView(mCurrentView, getChildLp(mCurrentView));
- addView(mActions, getChildLp(mActions));
- mShortcutSize = getContext().getResources()
- .getDimensionPixelSize(R.dimen.slice_shortcut_size);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- measureChildren(widthMeasureSpec, heightMeasureSpec);
- int actionHeight = mActions.getVisibility() != View.GONE
- ? mActions.getMeasuredHeight()
- : 0;
- int newHeightSpec = MeasureSpec.makeMeasureSpec(
- mCurrentView.getMeasuredHeight() + actionHeight, MeasureSpec.EXACTLY);
- int width = MeasureSpec.getSize(widthMeasureSpec);
- setMeasuredDimension(width, newHeightSpec);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- mCurrentView.layout(l, t, l + mCurrentView.getMeasuredWidth(),
- t + mCurrentView.getMeasuredHeight());
- if (mActions.getVisibility() != View.GONE) {
- mActions.layout(l, mCurrentView.getMeasuredHeight(), l + mActions.getMeasuredWidth(),
- mCurrentView.getMeasuredHeight() + mActions.getMeasuredHeight());
- }
- }
-
- /**
- * Populates this view with the {@link Slice} associated with the provided {@link Intent}. To
- * use this method your app must have the permission
- * {@link android.Manifest.permission#BIND_SLICE}).
- * <p>
- * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is
- * updated with the slice identified by the provided intent changes. The lifecycle of this
- * observer is handled by SliceView in {@link #onAttachedToWindow()} and
- * {@link #onDetachedFromWindow()}. To unregister this observer outside of that you can call
- * {@link #clearSlice}.
- *
- * @return true if a slice was found for the provided intent.
- * @hide
- */
- public boolean setSlice(@Nullable Intent intent) {
- Slice s = Slice.bindSlice(mContext, intent);
- if (s != null) {
- return setSlice(s.getUri());
- }
- return s != null;
- }
-
- /**
- * Populates this view with the {@link Slice} associated with the provided {@link Uri}. To use
- * this method your app must have the permission
- * {@link android.Manifest.permission#BIND_SLICE}).
- * <p>
- * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is
- * updated when the slice identified by the provided URI changes. The lifecycle of this observer
- * is handled by SliceView in {@link #onAttachedToWindow()} and {@link #onDetachedFromWindow()}.
- * To unregister this observer outside of that you can call {@link #clearSlice}.
- *
- * @return true if a slice was found for the provided uri.
- */
- public boolean setSlice(@NonNull Uri sliceUri) {
- Preconditions.checkNotNull(sliceUri,
- "Uri cannot be null, to remove the slice use clearSlice()");
- if (sliceUri == null) {
- clearSlice();
- return false;
- }
- validate(sliceUri);
- Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
- if (s != null) {
- if (mObserver != null) {
- getContext().getContentResolver().unregisterContentObserver(mObserver);
- }
- mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
- if (isAttachedToWindow()) {
- registerSlice(sliceUri);
- }
- mCurrentSlice = s;
- reinflate();
- }
- return s != null;
- }
-
- /**
- * Populates this view to the provided {@link Slice}.
- * <p>
- * This does not register a content observer on the URI that the slice is backed by so it will
- * not update if the content changes. To have the view update when the content changes use
- * {@link #setSlice(Uri)} instead. Unlike {@link #setSlice(Uri)}, this method does not require
- * any special permissions.
- */
- public void showSlice(@NonNull Slice slice) {
- Preconditions.checkNotNull(slice,
- "Slice cannot be null, to remove the slice use clearSlice()");
- clearSlice();
- mCurrentSlice = slice;
- reinflate();
- }
-
- /**
- * Unregisters the change observer that is set when using {@link #setSlice}. Normally this is
- * done automatically during {@link #onDetachedFromWindow()}.
- * <p>
- * It is safe to call this method multiple times.
- */
- public void clearSlice() {
- mCurrentSlice = null;
- if (mObserver != null) {
- getContext().getContentResolver().unregisterContentObserver(mObserver);
- mObserver = null;
- }
- }
-
- /**
- * Set the mode this view should present in.
- */
- public void setMode(@SliceMode String mode) {
- setMode(mode, false /* animate */);
- }
-
- /**
- * Set whether this view should allow scrollable content when presenting in {@link #MODE_LARGE}.
- */
- public void setScrollable(boolean isScrollable) {
- mIsScrollable = isScrollable;
- reinflate();
- }
-
- /**
- * @hide
- */
- public void setMode(@SliceMode String mode, boolean animate) {
- if (animate) {
- Log.e(TAG, "Animation not supported yet");
- }
- mMode = mode;
- reinflate();
- }
-
- /**
- * @return the mode this view is presenting in.
- */
- public @SliceMode String getMode() {
- if (mMode.equals(MODE_AUTO)) {
- return MODE_LARGE;
- }
- return mMode;
- }
-
- /**
- * @hide
- *
- * Whether this view should show a row of actions with it.
- */
- public void setShowActionRow(boolean show) {
- mShowActions = show;
- reinflate();
- }
-
- private SliceModeView createView(String mode) {
- switch (mode) {
- case MODE_SHORTCUT:
- return new ShortcutView(getContext());
- case MODE_SMALL:
- return new SmallTemplateView(getContext());
- }
- return new LargeTemplateView(getContext());
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- registerSlice(mCurrentSlice != null ? mCurrentSlice.getUri() : null);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mObserver != null) {
- getContext().getContentResolver().unregisterContentObserver(mObserver);
- mObserver = null;
- }
- }
-
- private void registerSlice(Uri sliceUri) {
- if (sliceUri == null || mObserver == null) {
- return;
- }
- mContext.getContentResolver().registerContentObserver(sliceUri,
- false /* notifyForDescendants */, mObserver);
- }
-
- private void reinflate() {
- if (mCurrentSlice == null) {
- return;
- }
- // TODO: Smarter mapping here from one state to the next.
- SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
- List<SliceItem> items = mCurrentSlice.getItems();
- SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
- Slice.HINT_ACTIONS,
- Slice.HINT_ALT);
- String mode = getMode();
- if (!mode.equals(mCurrentView.getMode())) {
- removeAllViews();
- mCurrentView = createView(mode);
- addView(mCurrentView, getChildLp(mCurrentView));
- addView(mActions, getChildLp(mActions));
- }
- if (mode.equals(MODE_LARGE)) {
- ((LargeTemplateView) mCurrentView).setScrollable(mIsScrollable);
- }
- if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
- mCurrentView.setVisibility(View.VISIBLE);
- mCurrentView.setSlice(mCurrentSlice);
- } else {
- mCurrentView.setVisibility(View.GONE);
- }
-
- boolean showActions = mShowActions && actionRow != null
- && !mode.equals(MODE_SHORTCUT);
- if (showActions) {
- mActions.setActions(actionRow, color);
- mActions.setVisibility(View.VISIBLE);
- } else {
- mActions.setVisibility(View.GONE);
- }
- }
-
- private LayoutParams getChildLp(View child) {
- if (child instanceof ShortcutView) {
- return new LayoutParams(mShortcutSize, mShortcutSize);
- } else {
- return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
- }
- }
-
- private static void validate(Uri sliceUri) {
- if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
- throw new RuntimeException("Invalid uri " + sliceUri);
- }
- if (sliceUri.getPathSegments().size() == 0) {
- throw new RuntimeException("Invalid uri " + sliceUri);
- }
- }
-
- private class SliceObserver extends ContentObserver {
- SliceObserver(Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- this.onChange(selfChange, null);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- Slice s = Slice.bindSlice(mContext.getContentResolver(), uri);
- mCurrentSlice = s;
- reinflate();
- }
- }
-}
diff --git a/android/app/slice/widget/SliceViewUtil.java b/android/app/slice/widget/SliceViewUtil.java
deleted file mode 100644
index 1cf0055b..00000000
--- a/android/app/slice/widget/SliceViewUtil.java
+++ /dev/null
@@ -1,198 +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.app.slice.widget;
-
-import android.annotation.ColorInt;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.view.Gravity;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-/**
- * A bunch of utilities for slice UI.
- *
- * @hide
- */
-public class SliceViewUtil {
-
- /**
- * @hide
- */
- @ColorInt
- public static int getColorAccent(Context context) {
- return getColorAttr(context, android.R.attr.colorAccent);
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int getColorError(Context context) {
- return getColorAttr(context, android.R.attr.colorError);
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int getDefaultColor(Context context, int resId) {
- final ColorStateList list = context.getResources().getColorStateList(resId,
- context.getTheme());
-
- return list.getDefaultColor();
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int getDisabled(Context context, int inputColor) {
- return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int applyAlphaAttr(Context context, int attr, int inputColor) {
- TypedArray ta = context.obtainStyledAttributes(new int[] {
- attr
- });
- float alpha = ta.getFloat(0, 0);
- ta.recycle();
- return applyAlpha(alpha, inputColor);
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int applyAlpha(float alpha, int inputColor) {
- alpha *= Color.alpha(inputColor);
- return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
- Color.blue(inputColor));
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int getColorAttr(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[] {
- attr
- });
- @ColorInt
- int colorAccent = ta.getColor(0, 0);
- ta.recycle();
- return colorAccent;
- }
-
- /**
- * @hide
- */
- public static int getThemeAttr(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[] {
- attr
- });
- int theme = ta.getResourceId(0, 0);
- ta.recycle();
- return theme;
- }
-
- /**
- * @hide
- */
- public static Drawable getDrawable(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[] {
- attr
- });
- Drawable drawable = ta.getDrawable(0);
- ta.recycle();
- return drawable;
- }
-
- /**
- * @hide
- */
- public static Icon createIconFromDrawable(Drawable d) {
- if (d instanceof BitmapDrawable) {
- return Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
- }
- Bitmap b = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
- Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(b);
- d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
- d.draw(canvas);
- return Icon.createWithBitmap(b);
- }
-
- /**
- * @hide
- */
- public static void createCircledIcon(Context context, int color, int iconSize, Icon icon,
- boolean isLarge, ViewGroup parent) {
- ImageView v = new ImageView(context);
- v.setImageIcon(icon);
- parent.addView(v);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
- if (isLarge) {
- // XXX better way to convert from icon -> bitmap or crop an icon (?)
- Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
- Canvas iconCanvas = new Canvas(iconBm);
- v.layout(0, 0, iconSize, iconSize);
- v.draw(iconCanvas);
- v.setImageBitmap(getCircularBitmap(iconBm));
- } else {
- v.setColorFilter(Color.WHITE);
- }
- lp.width = iconSize;
- lp.height = iconSize;
- lp.gravity = Gravity.CENTER;
- }
-
- /**
- * @hide
- */
- public static Bitmap getCircularBitmap(Bitmap bitmap) {
- Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
- bitmap.getHeight(), Config.ARGB_8888);
- Canvas canvas = new Canvas(output);
- final Paint paint = new Paint();
- final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
- paint.setAntiAlias(true);
- canvas.drawARGB(0, 0, 0, 0);
- canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
- bitmap.getWidth() / 2, paint);
- paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
- canvas.drawBitmap(bitmap, rect, rect, paint);
- return output;
- }
-}
diff --git a/android/app/slice/widget/SmallTemplateView.java b/android/app/slice/widget/SmallTemplateView.java
deleted file mode 100644
index 1c4c5df2..00000000
--- a/android/app/slice/widget/SmallTemplateView.java
+++ /dev/null
@@ -1,211 +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.app.slice.widget;
-
-import android.app.PendingIntent.CanceledException;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.widget.LargeSliceAdapter.SliceListView;
-import android.app.slice.widget.SliceView.SliceModeView;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-import java.text.Format;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-/**
- * Small template is also used to construct list items for use with {@link LargeTemplateView}.
- *
- * @hide
- */
-public class SmallTemplateView extends SliceModeView implements SliceListView {
-
- private static final String TAG = "SmallTemplateView";
-
- private int mIconSize;
- private int mPadding;
-
- private LinearLayout mStartContainer;
- private TextView mTitleText;
- private TextView mSecondaryText;
- private LinearLayout mEndContainer;
-
- public SmallTemplateView(Context context) {
- super(context);
- inflate(context, R.layout.slice_small_template, this);
- mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
- mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.slice_padding);
-
- mStartContainer = (LinearLayout) findViewById(android.R.id.icon_frame);
- mTitleText = (TextView) findViewById(android.R.id.title);
- mSecondaryText = (TextView) findViewById(android.R.id.summary);
- mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame);
- }
-
- @Override
- public String getMode() {
- return SliceView.MODE_SMALL;
- }
-
- @Override
- public void setSliceItem(SliceItem slice) {
- resetViews();
- SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
- int color = colorItem != null ? colorItem.getColor() : -1;
-
- // Look for any title elements
- List<SliceItem> titleItems = SliceQuery.findAll(slice, -1, Slice.HINT_TITLE,
- null);
- boolean hasTitleText = false;
- boolean hasTitleItem = false;
- for (int i = 0; i < titleItems.size(); i++) {
- SliceItem item = titleItems.get(i);
- if (!hasTitleItem) {
- // icon, action icon, or timestamp
- if (item.getType() == SliceItem.TYPE_ACTION) {
- hasTitleItem = addIcon(item, color, mStartContainer);
- } else if (item.getType() == SliceItem.TYPE_IMAGE) {
- addIcon(item, color, mStartContainer);
- hasTitleItem = true;
- } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
- TextView tv = new TextView(getContext());
- tv.setText(convertTimeToString(item.getTimestamp()));
- hasTitleItem = true;
- }
- }
- if (!hasTitleText && item.getType() == SliceItem.TYPE_TEXT) {
- mTitleText.setText(item.getText());
- hasTitleText = true;
- }
- if (hasTitleText && hasTitleItem) {
- break;
- }
- }
- mTitleText.setVisibility(hasTitleText ? View.VISIBLE : View.GONE);
- mStartContainer.setVisibility(hasTitleItem ? View.VISIBLE : View.GONE);
-
- if (slice.getType() != SliceItem.TYPE_SLICE) {
- return;
- }
-
- // Deal with remaining items
- int itemCount = 0;
- boolean hasSummary = false;
- ArrayList<SliceItem> sliceItems = new ArrayList<SliceItem>(
- slice.getSlice().getItems());
- for (int i = 0; i < sliceItems.size(); i++) {
- SliceItem item = sliceItems.get(i);
- if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
- && !item.hasHint(Slice.HINT_TITLE)) {
- // TODO -- Should combine all text items?
- mSecondaryText.setText(item.getText());
- hasSummary = true;
- }
- if (itemCount <= 3) {
- if (item.getType() == SliceItem.TYPE_ACTION) {
- if (addIcon(item, color, mEndContainer)) {
- itemCount++;
- }
- } else if (item.getType() == SliceItem.TYPE_IMAGE) {
- addIcon(item, color, mEndContainer);
- itemCount++;
- } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
- TextView tv = new TextView(getContext());
- tv.setText(convertTimeToString(item.getTimestamp()));
- mEndContainer.addView(tv);
- itemCount++;
- } else if (item.getType() == SliceItem.TYPE_SLICE) {
- List<SliceItem> subItems = item.getSlice().getItems();
- for (int j = 0; j < subItems.size(); j++) {
- sliceItems.add(subItems.get(j));
- }
- }
- }
- }
- }
-
- @Override
- public void setSlice(Slice slice) {
- setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE,
- slice.getHints().toArray(new String[slice.getHints().size()])));
- }
-
- /**
- * @return Whether an icon was added.
- */
- private boolean addIcon(SliceItem sliceItem, int color, LinearLayout container) {
- SliceItem image = null;
- SliceItem action = null;
- if (sliceItem.getType() == SliceItem.TYPE_ACTION) {
- image = SliceQuery.find(sliceItem.getSlice(), SliceItem.TYPE_IMAGE);
- action = sliceItem;
- } else if (sliceItem.getType() == SliceItem.TYPE_IMAGE) {
- image = sliceItem;
- }
- if (image != null) {
- ImageView iv = new ImageView(getContext());
- iv.setImageIcon(image.getIcon());
- if (action != null) {
- final SliceItem sliceAction = action;
- iv.setOnClickListener(v -> AsyncTask.execute(
- () -> {
- try {
- sliceAction.getAction().send();
- } catch (CanceledException e) {
- e.printStackTrace();
- }
- }));
- iv.setBackground(SliceViewUtil.getDrawable(getContext(),
- android.R.attr.selectableItemBackground));
- }
- if (color != -1 && !sliceItem.hasHint(Slice.HINT_NO_TINT)) {
- iv.setColorFilter(color);
- }
- container.addView(iv);
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams();
- lp.width = mIconSize;
- lp.height = mIconSize;
- lp.setMarginStart(mPadding);
- return true;
- }
- return false;
- }
-
- private String convertTimeToString(long time) {
- // TODO -- figure out what format(s) we support
- Date date = new Date(time);
- Format format = new SimpleDateFormat("MM dd yyyy HH:mm:ss");
- return format.format(date);
- }
-
- private void resetViews() {
- mStartContainer.removeAllViews();
- mEndContainer.removeAllViews();
- mTitleText.setText(null);
- mSecondaryText.setText(null);
- }
-}
diff --git a/android/app/usage/UsageEvents.java b/android/app/usage/UsageEvents.java
index 0d7a9413..8200414f 100644
--- a/android/app/usage/UsageEvents.java
+++ b/android/app/usage/UsageEvents.java
@@ -100,6 +100,12 @@ public final class UsageEvents implements Parcelable {
*/
public static final int CHOOSER_ACTION = 9;
+ /**
+ * An event type denoting that a notification was viewed by the user.
+ * @hide
+ */
+ public static final int NOTIFICATION_SEEN = 10;
+
/** @hide */
public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
diff --git a/android/app/usage/UsageStatsManagerInternal.java b/android/app/usage/UsageStatsManagerInternal.java
index 29e7439f..9954484f 100644
--- a/android/app/usage/UsageStatsManagerInternal.java
+++ b/android/app/usage/UsageStatsManagerInternal.java
@@ -16,6 +16,7 @@
package android.app.usage;
+import android.app.usage.AppStandby.StandbyBuckets;
import android.content.ComponentName;
import android.content.res.Configuration;
@@ -91,6 +92,19 @@ public abstract class UsageStatsManagerInternal {
public abstract boolean isAppIdle(String packageName, int uidForAppId, int userId);
/**
+ * Returns the app standby bucket that the app is currently in. This accessor does
+ * <em>not</em> obfuscate instant apps.
+ *
+ * @param packageName
+ * @param userId
+ * @param nowElapsed The current time, in the elapsedRealtime time base
+ * @return the AppStandby bucket code the app currently resides in. If the app is
+ * unknown in the given user, STANDBY_BUCKET_NEVER is returned.
+ */
+ @StandbyBuckets public abstract int getAppStandbyBucket(String packageName, int userId,
+ long nowElapsed);
+
+ /**
* Returns all of the uids for a given user where all packages associating with that uid
* are in the app idle state -- there are no associated apps that are not idle. This means
* all of the returned uids can be safely considered app idle.
diff --git a/android/arch/paging/BoundedDataSource.java b/android/arch/paging/BoundedDataSource.java
deleted file mode 100644
index 06564907..00000000
--- a/android/arch/paging/BoundedDataSource.java
+++ /dev/null
@@ -1,80 +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.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Simplest data source form that provides all of its data through a single loadRange() method.
- * <p>
- * Requires that your data resides in positions <code>0</code> through <code>N</code>, where
- * <code>N</code> is the value returned from {@link #countItems()}. You must return the exact number
- * requested, so that the data as returned can be safely prepended/appended to what has already
- * been loaded.
- * <p>
- * For more flexibility in how many items to load, or to avoid counting your data source, override
- * {@link PositionalDataSource} directly.
- *
- * @param <Value> Value type returned by the data source.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class BoundedDataSource<Value> extends PositionalDataSource<Value> {
- /**
- * Called to load items at from the specified position range.
- *
- * @param startPosition Index of first item to load.
- * @param loadCount Exact number of items to load. Returning a different number will cause
- * an exception to be thrown.
- * @return List of loaded items. Null if the BoundedDataSource is no longer valid, and should
- * not be queried again.
- */
- @WorkerThread
- @Nullable
- public abstract List<Value> loadRange(int startPosition, int loadCount);
-
- @WorkerThread
- @Nullable
- @Override
- public List<Value> loadAfter(int startIndex, int pageSize) {
- return loadRange(startIndex, pageSize);
- }
-
- @WorkerThread
- @Nullable
- @Override
- public List<Value> loadBefore(int startIndex, int pageSize) {
- if (startIndex < 0) {
- return new ArrayList<>();
- }
- int loadSize = Math.min(pageSize, startIndex + 1);
- startIndex = startIndex - loadSize + 1;
- List<Value> result = loadRange(startIndex, loadSize);
- if (result != null) {
- if (result.size() != loadSize) {
- throw new IllegalStateException("invalid number of items returned.");
- }
- }
- return result;
- }
-}
diff --git a/android/arch/paging/ContiguousDataSource.java b/android/arch/paging/ContiguousDataSource.java
index 414c4ffe..38b7cc04 100644
--- a/android/arch/paging/ContiguousDataSource.java
+++ b/android/arch/paging/ContiguousDataSource.java
@@ -19,7 +19,7 @@ package android.arch.paging;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import java.util.List;
+import java.util.concurrent.Executor;
abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
@Override
@@ -27,36 +27,15 @@ abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
return true;
}
- abstract void loadInitial(Key key, int initialLoadSize, boolean enablePlaceholders,
- @NonNull PageResult.Receiver<Key, Value> receiver);
+ abstract void loadInitial(@Nullable Key key, int initialLoadSize,
+ int pageSize, boolean enablePlaceholders,
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver);
- void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
- @NonNull PageResult.Receiver<Key, Value> receiver) {
- if (!isInvalid()) {
- List<Value> list = loadAfterImpl(currentEndIndex, currentEndItem, pageSize);
+ abstract void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver);
- if (list != null && !isInvalid()) {
- receiver.postOnPageResult(new PageResult<>(
- PageResult.APPEND, new Page<Key, Value>(list), 0, 0, 0));
- return;
- }
- }
- receiver.postOnPageResult(new PageResult<Key, Value>(PageResult.APPEND));
- }
-
- void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
- @NonNull PageResult.Receiver<Key, Value> receiver) {
- if (!isInvalid()) {
- List<Value> list = loadBeforeImpl(currentBeginIndex, currentBeginItem, pageSize);
-
- if (list != null && !isInvalid()) {
- receiver.postOnPageResult(new PageResult<>(
- PageResult.PREPEND, new Page<Key, Value>(list), 0, 0, 0));
- return;
- }
- }
- receiver.postOnPageResult(new PageResult<Key, Value>(PageResult.PREPEND));
- }
+ abstract void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver);
/**
* Get the key from either the position, or item, or null if position/item invalid.
@@ -65,12 +44,4 @@ abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
* 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);
}
diff --git a/android/arch/paging/ContiguousPagedList.java b/android/arch/paging/ContiguousPagedList.java
index cdff391d..a134e440 100644
--- a/android/arch/paging/ContiguousPagedList.java
+++ b/android/arch/paging/ContiguousPagedList.java
@@ -21,6 +21,7 @@ import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import java.util.List;
import java.util.concurrent.Executor;
class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback {
@@ -31,28 +32,14 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
private int mPrependItemsRequested = 0;
private int mAppendItemsRequested = 0;
- @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 PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
// Creation thread for initial synchronous load, otherwise main thread
// Safe to access main thread only state - no other thread has reference during construction
@AnyThread
@Override
- public void onPageResult(@NonNull PageResult<K, V> pageResult) {
- if (pageResult.page == null) {
+ public void onPageResult(@PageResult.ResultType int resultType,
+ @NonNull PageResult<V> pageResult) {
+ if (pageResult.isInvalid()) {
detach();
return;
}
@@ -62,25 +49,25 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
return;
}
- Page<K, V> page = pageResult.page;
- if (pageResult.type == PageResult.INIT) {
- mKeyedStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
+ List<V> page = pageResult.page;
+ if (resultType == PageResult.INIT) {
+ mStorage.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);
+ mLastLoad = pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
+ } else if (resultType == PageResult.APPEND) {
+ mStorage.appendPage(page, ContiguousPagedList.this);
+ } else if (resultType == PageResult.PREPEND) {
+ mStorage.prependPage(page, ContiguousPagedList.this);
}
if (mBoundaryCallback != null) {
boolean deferEmpty = mStorage.size() == 0;
boolean deferBegin = !deferEmpty
- && pageResult.type == PageResult.PREPEND
- && pageResult.page.items.size() == 0;
+ && resultType == PageResult.PREPEND
+ && pageResult.page.size() == 0;
boolean deferEnd = !deferEmpty
- && pageResult.type == PageResult.APPEND
- && pageResult.page.items.size() == 0;
+ && resultType == PageResult.APPEND
+ && pageResult.page.size() == 0;
deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
}
}
@@ -93,24 +80,27 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
@Nullable BoundaryCallback<V> boundaryCallback,
@NonNull Config config,
final @Nullable K key) {
- super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor,
+ super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
- // blocking init just triggers the initial load on the construction thread -
- // Could still be posted with callback, if desired.
- mDataSource.loadInitial(key,
- mConfig.initialLoadSizeHint,
- mConfig.enablePlaceholders,
- mReceiver);
+ if (mDataSource.isInvalid()) {
+ detach();
+ } else {
+ mDataSource.loadInitial(key,
+ mConfig.initialLoadSizeHint,
+ mConfig.pageSize,
+ mConfig.enablePlaceholders,
+ mMainThreadExecutor,
+ mReceiver);
+ }
}
@MainThread
@Override
void dispatchUpdatesSinceSnapshot(
@NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) {
-
- final PagedStorage<?, V> snapshot = pagedListSnapshot.mStorage;
+ final PagedStorage<V> snapshot = pagedListSnapshot.mStorage;
final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended();
final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended();
@@ -120,7 +110,8 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
// 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
+ if (snapshot.isEmpty()
+ || newlyAppended < 0
|| newlyPrepended < 0
|| mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0)
|| mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0)
@@ -190,7 +181,13 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
if (isDetached()) {
return;
}
- mDataSource.loadBefore(position, item, mConfig.pageSize, mReceiver);
+ if (mDataSource.isInvalid()) {
+ detach();
+ } else {
+ mDataSource.loadBefore(position, item, mConfig.pageSize,
+ mMainThreadExecutor, mReceiver);
+ }
+
}
});
}
@@ -213,7 +210,12 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
if (isDetached()) {
return;
}
- mDataSource.loadAfter(position, item, mConfig.pageSize, mReceiver);
+ if (mDataSource.isInvalid()) {
+ detach();
+ } else {
+ mDataSource.loadAfter(position, item, mConfig.pageSize,
+ mMainThreadExecutor, mReceiver);
+ }
}
});
}
diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java
index ff44521e..b82d4e6d 100644
--- a/android/arch/paging/DataSource.java
+++ b/android/arch/paging/DataSource.java
@@ -18,28 +18,71 @@ package android.arch.paging;
import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
+import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Base class for incremental data loading, used in list paging. To implement, extend either the
- * {@link KeyedDataSource}, or {@link TiledDataSource} subclass.
+ * Base class for loading pages of snapshot data into a {@link PagedList}.
* <p>
- * Choose based on whether each load operation is based on the position of the data in the list.
+ * DataSource is queried to load pages of content into a {@link PagedList}. A PagedList can grow as
+ * it loads more data, but the data loaded cannot be updated.
* <p>
- * Use {@link KeyedDataSource} if you need to use data from item <code>N-1</code> to load item
- * <code>N</code>. For example, if requesting the backend for the next comments in the list
+ * A PagedList / DataSource pair serve as a snapshot of the data set being loaded. If the
+ * underlying data set is modified, a new PagedList / DataSource pair must be created to represent
+ * the new data.
+ * <h4>Loading Pages</h4>
+ * PagedList queries data from its DataSource in response to loading hints. {@link PagedListAdapter}
+ * calls {@link PagedList#loadAround(int)} to load content as the user scrolls in a RecyclerView.
+ * <p>
+ * To control how and when a PagedList queries data from its DataSource, see
+ * {@link PagedList.Config}. The Config object defines things like load sizes and prefetch distance.
+ * <h4>Updating Paged Data</h4>
+ * A PagedList / DataSource pair are a snapshot of the data set. A new pair of
+ * PagedList / DataSource must be created if an update occurs, such as a reorder, insert, delete, or
+ * content update occurs. A DataSource must detect that it cannot continue loading its
+ * snapshot (for instance, when Database query notices a table being invalidated), and call
+ * {@link #invalidate()}. Then a new PagedList / DataSource pair would be created to load data from
+ * the new state of the Database query.
+ * <p>
+ * To page in data that doesn't update, you can create a single DataSource, and pass it to a single
+ * PagedList. For example, loading from network when the network's paging API doesn't provide
+ * updates.
+ * <p>
+ * To page in data from a source that does provide updates, you can create a
+ * {@link DataSource.Factory}, where each DataSource created is invalidated when an update to the
+ * data set occurs that makes the current snapshot invalid. For example, when paging a query from
+ * the Database, and the table being queried inserts or removes items. You can also use a
+ * DataSource.Factory to provide multiple versions of network-paged lists. If reloading all content
+ * (e.g. in response to an action like swipe-to-refresh) is required to get a new version of data,
+ * you can connect an explicit refresh signal to call {@link #invalidate()} on the current
+ * DataSource.
+ * <p>
+ * If you have more granular update signals, such as a network API signaling an update to a single
+ * item in the list, it's recommended to load data from network into memory. Then present that
+ * data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory
+ * copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the
+ * snapshot can be created.
+ * <h4>Implementing a DataSource</h4>
+ * To implement, extend either the {@link KeyedDataSource}, or {@link PositionalDataSource}
+ * subclass. Choose based on whether each load operation is based on the position of the data in the
+ * list.
+ * <p>
+ * Use {@link KeyedDataSource} if you need to use data from item {@code N-1} to load item
+ * {@code N}. For example, if requesting the backend for the next comments in the list
* requires the ID or timestamp of the most recent loaded comment, or if querying the next users
* from a name-sorted database query requires the name and unique ID of the previous.
* <p>
- * Use {@link TiledDataSource} if you can load arbitrary pages based solely on position information,
- * and can provide a fixed item count. TiledDataSource supports querying pages at arbitrary
- * positions, so can provide data to PagedLists in arbitrary order.
+ * Use {@link PositionalDataSource} if you can load arbitrary pages based solely on position
+ * information, and can provide a fixed item count. PositionalDataSource supports querying pages at
+ * arbitrary positions, so can provide data to PagedLists in arbitrary order.
* <p>
- * Because a <code>null</code> item indicates a placeholder in {@link PagedList}, DataSource may not
- * return <code>null</code> items in lists that it loads. This is so that users of the PagedList
+ * Because a {@code null} item indicates a placeholder in {@link PagedList}, DataSource may not
+ * return {@code null} items in lists that it loads. This is so that users of the PagedList
* can differentiate unloaded placeholder items from content that has been paged in.
*
* @param <Key> Input used to trigger initial load from the DataSource. Often an Integer position.
@@ -47,8 +90,36 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/
@SuppressWarnings("unused") // suppress warning to remove Key/Value, needed for subclass type safety
public abstract class DataSource<Key, Value> {
-
+ /**
+ * Factory for DataSources.
+ * <p>
+ * Data-loading systems of an application or library can implement this interface to allow
+ * {@code LiveData<PagedList>}s to be created. For example, Room can provide a
+ * DataSource.Factory for a given SQL query:
+ *
+ * <pre>
+ * {@literal @}Dao
+ * interface UserDao {
+ * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
+ * public abstract DataSource.Factory&lt;Integer, User> usersByLastName();
+ * }
+ * </pre>
+ * In the above sample, {@code Integer} is used because it is the {@code Key} type of
+ * PositionalDataSource. Currently, Room uses the {@code LIMIT}/{@code OFFSET} SQL keywords to
+ * page a large query with a PositionalDataSource.
+ *
+ * @param <Key> Key identifying items in DataSource.
+ * @param <Value> Type of items in the list loaded by the DataSources.
+ */
public interface Factory<Key, Value> {
+ /**
+ * Create a DataSource.
+ * <p>
+ * The DataSource should invalidate itself if the snapshot is no longer valid, and a new
+ * DataSource should be queried from the Factory.
+ *
+ * @return the new DataSource.
+ */
DataSource<Key, Value> create();
}
@@ -58,18 +129,77 @@ public abstract class DataSource<Key, Value> {
}
/**
- * If returned by countItems(), indicates an undefined number of items are provided by the data
- * source. Continued querying in either direction may continue to produce more data.
- */
- @SuppressWarnings("WeakerAccess")
- public static int COUNT_UNDEFINED = -1;
-
- /**
* Returns true if the data source guaranteed to produce a contiguous set of items,
* never producing gaps.
*/
abstract boolean isContiguous();
+ static class BaseLoadCallback<T> {
+ static void validateInitialLoadParams(@NonNull List<?> data, int position, int totalCount) {
+ if (position < 0) {
+ throw new IllegalArgumentException("Position must be non-negative");
+ }
+ if (data.size() + position > totalCount) {
+ throw new IllegalArgumentException(
+ "List size + position too large, last item in list beyond totalCount.");
+ }
+ if (data.size() == 0 && totalCount > 0) {
+ throw new IllegalArgumentException(
+ "Initial result cannot be empty if items are present in data set.");
+ }
+ }
+
+ @PageResult.ResultType
+ final int mResultType;
+ private final DataSource mDataSource;
+ private final PageResult.Receiver<T> mReceiver;
+
+ // mSignalLock protects mPostExecutor, and mHasSignalled
+ private final Object mSignalLock = new Object();
+ private Executor mPostExecutor = null;
+ private boolean mHasSignalled = false;
+
+ BaseLoadCallback(@PageResult.ResultType int resultType, @NonNull DataSource dataSource,
+ @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+ mResultType = resultType;
+ mPostExecutor = mainThreadExecutor;
+ mDataSource = dataSource;
+ mReceiver = receiver;
+ }
+
+ void setPostExecutor(Executor postExecutor) {
+ synchronized (mSignalLock) {
+ mPostExecutor = postExecutor;
+ }
+ }
+
+ void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
+ Executor executor;
+ synchronized (mSignalLock) {
+ if (mHasSignalled) {
+ throw new IllegalStateException(
+ "LoadCallback already dispatched, cannot dispatch again.");
+ }
+ mHasSignalled = true;
+ executor = mPostExecutor;
+ }
+
+ final PageResult<T> resolvedResult =
+ mDataSource.isInvalid() ? PageResult.<T>getInvalidResult() : result;
+
+ if (executor != null) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mReceiver.onPageResult(mResultType, result);
+ }
+ });
+ } else {
+ mReceiver.onPageResult(mResultType, result);
+ }
+ }
+ }
+
/**
* Invalidation callback for DataSource.
* <p>
diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java
index 3214a4ef..4f62692b 100644
--- a/android/arch/paging/KeyedDataSource.java
+++ b/android/arch/paging/KeyedDataSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,328 +16,245 @@
package android.arch.paging;
-import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* Incremental data loader for paging keyed content, where loaded content uses previously loaded
* items as input to future loads.
* <p>
- * Implement a DataSource using KeyedDataSource if you need to use data from item <code>N-1</code>
- * to load item <code>N</code>. This is common, for example, in sorted database queries where
+ * Implement a DataSource using KeyedDataSource if you need to use data from item {@code N - 1}
+ * to load item {@code N}. This is common, for example, in sorted database queries where
* attributes of the item such just before the next query define how to execute it.
- * <p>
- * A compute usage pattern with Room SQL queries would look like this (though note, Room plans to
- * provide generation of much of this code in the future):
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- * {@literal @}Query("SELECT * from user ORDER BY name DESC LIMIT :limit")
- * public abstract List&lt;User> userNameInitial(int limit);
- *
- * {@literal @}Query("SELECT * from user WHERE name &lt; :key ORDER BY name DESC LIMIT :limit")
- * public abstract List&lt;User> userNameLoadAfter(String key, int limit);
- *
- * {@literal @}Query("SELECT * from user WHERE name > :key ORDER BY name ASC LIMIT :limit")
- * public abstract List&lt;User> userNameLoadBefore(String key, int limit);
- * }
- *
- * public class KeyedUserQueryDataSource extends KeyedDataSource&lt;String, User> {
- * private MyDatabase mDb;
- * private final UserDao mUserDao;
- * {@literal @}SuppressWarnings("FieldCanBeLocal")
- * private final InvalidationTracker.Observer mObserver;
- *
- * public KeyedUserQueryDataSource(MyDatabase db) {
- * mDb = db;
- * mUserDao = db.getUserDao();
- * mObserver = new InvalidationTracker.Observer("user") {
- * {@literal @}Override
- * public void onInvalidated({@literal @}NonNull Set&lt;String> tables) {
- * // the user table has been invalidated, invalidate the DataSource
- * invalidate();
- * }
- * };
- * db.getInvalidationTracker().addWeakObserver(mObserver);
- * }
- *
- * {@literal @}Override
- * public boolean isInvalid() {
- * mDb.getInvalidationTracker().refreshVersionsSync();
- * return super.isInvalid();
- * }
- *
- * {@literal @}Override
- * public String getKey({@literal @}NonNull User item) {
- * return item.getName();
- * }
- *
- * {@literal @}Override
- * public List&lt;User> loadInitial(int pageSize) {
- * return mUserDao.userNameInitial(pageSize);
- * }
- *
- * {@literal @}Override
- * public List&lt;User> loadBefore({@literal @}NonNull String userName, int pageSize) {
- * // Return items adjacent to 'userName' in reverse order
- * // it's valid to return a different-sized list of items than pageSize, if it's easier
- * return mUserDao.userNameLoadBefore(userName, pageSize);
- * }
- *
- * {@literal @}Override
- * public List&lt;User> loadAfter({@literal @}Nullable String userName, int pageSize) {
- * // Return items adjacent to 'userName'
- * // it's valid to return a different-sized list of items than pageSize, if it's easier
- * return mUserDao.userNameLoadAfter(userName, pageSize);
- * }
- * }</pre>
*
* @param <Key> Type of data used to query Value types out of the DataSource.
* @param <Value> Type of items being loaded by the DataSource.
*/
public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
- @Nullable
- @Override
- List<Value> loadAfterImpl(int currentEndIndex, @NonNull Value currentEndItem, int pageSize) {
- return loadAfter(getKey(currentEndItem), pageSize);
- }
-
- @Nullable
- @Override
- List<Value> loadBeforeImpl(
- int currentBeginIndex, @NonNull Value currentBeginItem, int 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;
- }
-
-
- @Override
- void loadInitial(Key key, int initialLoadSize, boolean enablePlaceholders,
- @NonNull PageResult.Receiver<Key, Value> receiver) {
-
- PageResult<Key, Value> pageResult =
- loadInitialInternal(key, initialLoadSize, enablePlaceholders);
- if (pageResult == null) {
- // loading failed, return empty page
- receiver.onPageResult(new PageResult<Key, Value>(PageResult.INIT));
- } else {
- receiver.onPageResult(pageResult);
- }
- }
-
/**
- * Try initial load, and either return the successful initial load to the receiver,
- * or null if unsuccessful.
+ * Callback for KeyedDataSource initial loading methods to return data and (optionally)
+ * position/count information.
+ * <p>
+ * A callback can be called only once, and will throw if called again.
+ * <p>
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param <T> Type of items being loaded.
*/
- @Nullable
- private PageResult<Key, Value> loadInitialInternal(
- @Nullable Key key, int initialLoadSize, boolean enablePlaceholders) {
- // check if invalid at beginning, and before returning a valid list
- if (isInvalid()) {
- return null;
+ public static class InitialLoadCallback<T> extends LoadCallback<T> {
+ private final boolean mCountingEnabled;
+ InitialLoadCallback(@NonNull KeyedDataSource dataSource, boolean countingEnabled,
+ @NonNull PageResult.Receiver<T> receiver) {
+ super(dataSource, PageResult.INIT, null, receiver);
+ mCountingEnabled = countingEnabled;
}
- List<Value> list;
- if (key == null) {
- // no key, so load initial.
- list = loadInitial(initialLoadSize);
- if (list == null) {
- return null;
- }
- } else {
- List<Value> after = loadAfter(key, initialLoadSize / 2);
- if (after == null) {
- return null;
- }
+ /**
+ * Called to pass initial load state from a DataSource.
+ * <p>
+ * Call this method from your DataSource's {@code loadInitial} function to return data,
+ * and inform how many placeholders should be shown before and after. If counting is cheap
+ * to compute (for example, if a network load returns the information regardless), it's
+ * recommended to pass data back through this method.
+ * <p>
+ * It is always valid to pass a different amount of data than what is requested. Pass an
+ * empty list if there is no more data to load.
+ *
+ * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+ * is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are {@code N}
+ * items before the items in data that can be loaded from this DataSource,
+ * pass {@code N}.
+ * @param totalCount Total number of items that may be returned from this DataSource.
+ * Includes the number in the initial {@code data} parameter
+ * as well as any items that can be loaded in front or behind of
+ * {@code data}.
+ */
+ public void onResult(@NonNull List<T> data, int position, int totalCount) {
+ validateInitialLoadParams(data, position, totalCount);
- Key loadBeforeKey = after.isEmpty() ? key : getKey(after.get(0));
- List<Value> before = loadBefore(loadBeforeKey, initialLoadSize / 2);
- if (before == null) {
- return null;
- }
- if (!after.isEmpty() || !before.isEmpty()) {
- // one of the lists has data
- if (after.isEmpty()) {
- // retry loading after, since it may be that the key passed points to the end of
- // the list, so we need to load after the last item in the before list
- after = loadAfter(getKey(before.get(0)), initialLoadSize / 2);
- if (after == null) {
- return null;
- }
- }
- // assemble full list
- list = new ArrayList<>();
- list.addAll(before);
- // Note - we reverse the list instead of before, in case before is immutable
- Collections.reverse(list);
- list.addAll(after);
+ int trailingUnloadedCount = totalCount - position - data.size();
+ if (mCountingEnabled) {
+ dispatchResultToReceiver(new PageResult<>(
+ data, position, trailingUnloadedCount, 0));
} else {
- // load before(key) and load after(key) failed - try load initial to be *sure* we
- // catch the case where there's only one item, which is loaded by the key case
- list = loadInitial(initialLoadSize);
- if (list == null) {
- return null;
- }
+ dispatchResultToReceiver(new PageResult<>(data, position));
}
}
+ }
- final Page<Key, Value> page = new Page<>(list);
-
- if (list.isEmpty()) {
- if (isInvalid()) {
- return null;
- }
- // wasn't able to load any items, but not invalid - return an empty page.
- return new PageResult<>(PageResult.INIT, page, 0, 0, 0);
+ /**
+ * Callback for KeyedDataSource {@link #loadBefore(Object, int, LoadCallback)}
+ * and {@link #loadAfter(Object, int, LoadCallback)} methods to return data.
+ * <p>
+ * A callback can be called only once, and will throw if called again.
+ * <p>
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param <T> Type of items being loaded.
+ */
+ public static class LoadCallback<T> extends BaseLoadCallback<T> {
+ LoadCallback(@NonNull KeyedDataSource dataSource, @PageResult.ResultType int type,
+ @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+ super(type, dataSource, mainThreadExecutor, receiver);
}
- int itemsBefore = COUNT_UNDEFINED;
- int itemsAfter = COUNT_UNDEFINED;
- if (enablePlaceholders) {
- itemsBefore = countItemsBefore(getKey(list.get(0)));
- itemsAfter = countItemsAfter(getKey(list.get(list.size() - 1)));
+ /**
+ * Called to pass loaded data from a DataSource.
+ * <p>
+ * Call this method from your KeyedDataSource's
+ * {@link #loadBefore(Object, int, LoadCallback)} and
+ * {@link #loadAfter(Object, int, LoadCallback)} methods to return data.
+ * <p>
+ * Call this from {@link #loadInitial(Object, int, boolean, InitialLoadCallback)} to
+ * initialize without counting available data, or supporting placeholders.
+ * <p>
+ * It is always valid to pass a different amount of data than what is requested. Pass an
+ * empty list if there is no more data to load.
+ *
+ * @param data List of items loaded from the KeyedDataSource.
+ */
+ public void onResult(@NonNull List<T> data) {
+ dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
}
+ }
- if (isInvalid()) {
+ @Nullable
+ @Override
+ final Key getKey(int position, Value item) {
+ if (item == null) {
return null;
}
- if (itemsBefore == COUNT_UNDEFINED || itemsAfter == COUNT_UNDEFINED) {
- itemsBefore = 0;
- itemsAfter = 0;
- }
- return new PageResult<>(
- PageResult.INIT,
- page,
- itemsBefore,
- itemsAfter,
- 0);
+
+ return getKey(item);
}
- /**
- * Return a key associated with the given item.
- * <p>
- * If your KeyedDataSource is loading from a source that is sorted and loaded by a unique
- * integer ID, you would return {@code item.getID()} here. This key can then be passed to
- * {@link #loadBefore(Key, int)} or {@link #loadAfter(Key, int)} to load additional items
- * adjacent to the item passed to this function.
- * <p>
- * If your key is more complex, such as when you're sorting by name, then resolving collisions
- * with integer ID, you'll need to return both. In such a case you would use a wrapper class,
- * such as {@code Pair<String, Integer>} or, in Kotlin,
- * {@code data class Key(val name: String, val id: Int)}
- *
- * @param item Item to get the key from.
- * @return Key associated with given item.
- */
- @NonNull
- @AnyThread
- public abstract Key getKey(@NonNull Value item);
+ @Override
+ public void loadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
+ boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
+ @NonNull PageResult.Receiver<Value> receiver) {
+ InitialLoadCallback<Value> callback =
+ new InitialLoadCallback<>(this, enablePlaceholders, receiver);
+ loadInitial(key, initialLoadSize, enablePlaceholders, callback);
- /**
- * Return the number of items that occur before the item uniquely identified by {@code key} in
- * the data set.
- * <p>
- * For example, if you're loading items sorted by ID, then this would return the total number of
- * items with ID less than {@code key}.
- * <p>
- * If you return {@link #COUNT_UNDEFINED} here, or from {@link #countItemsAfter(Key)}, your
- * data source will not present placeholder null items in place of unloaded data.
- *
- * @param key A unique identifier of an item in the data set.
- * @return Number of items in the data set before the item identified by {@code key}, or
- * {@link #COUNT_UNDEFINED}.
- *
- * @see #countItemsAfter(Key)
- */
- @WorkerThread
- public int countItemsBefore(@NonNull Key key) {
- return COUNT_UNDEFINED;
+ // If initialLoad's callback is not called within the body, we force any following calls
+ // to post to the UI thread. This constructor may be run on a background thread, but
+ // after constructor, mutation must happen on UI thread.
+ callback.setPostExecutor(mainThreadExecutor);
+ }
+
+ @Override
+ void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver) {
+ loadAfter(getKey(currentEndItem), pageSize,
+ new LoadCallback<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
+ }
+
+ @Override
+ void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver) {
+ loadBefore(getKey(currentBeginItem), pageSize,
+ new LoadCallback<>(this, PageResult.PREPEND, mainThreadExecutor, receiver));
}
/**
- * Return the number of items that occur after the item uniquely identified by {@code key} in
- * the data set.
+ * Load initial data.
* <p>
- * For example, if you're loading items sorted by ID, then this would return the total number of
- * items with ID greater than {@code key}.
+ * This method is called first to initialize a PagedList with data. If it's possible to count
+ * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
+ * the callback via the three-parameter
+ * {@link InitialLoadCallback#onResult(List, int, int)}. This enables PagedLists
+ * presenting data from this source to display placeholders to represent unloaded items.
* <p>
- * If you return {@link #COUNT_UNDEFINED} here, or from {@link #countItemsBefore(Key)}, your
- * data source will not present placeholder null items in place of unloaded data.
- *
- * @param key A unique identifier of an item in the data set.
- * @return Number of items in the data set after the item identified by {@code key}, or
- * {@link #COUNT_UNDEFINED}.
+ * {@code initialLoadKey} and {@code requestedLoadSize} are hints, not requirements, so if it is
+ * difficult or impossible to respect them, they may be altered. Note that ignoring the
+ * {@code initialLoadKey} can prevent subsequent PagedList/DataSource pairs from initializing at
+ * the same location. If your data source never invalidates (for example, loading from the
+ * network without the network ever signalling that old data must be reloaded), it's fine to
+ * ignore the {@code initialLoadKey} and always start from the beginning of the data set.
*
- * @see #countItemsBefore(Key)
+ * @param initialLoadKey Load items around this key, or at the beginning of the data set if null
+ * is passed.
+ * @param requestedLoadSize Suggested number of items to load.
+ * @param enablePlaceholders Signals whether counting is requested. If false, you can
+ * potentially save work by calling the single-parameter variant of
+ * {@link LoadCallback#onResult(List)} and not counting the
+ * number of items in the data set.
+ * @param callback DataSource.LoadCallback that receives initial load data.
*/
- @WorkerThread
- public int countItemsAfter(@NonNull Key key) {
- return COUNT_UNDEFINED;
- }
-
- @WorkerThread
- @Nullable
- public abstract List<Value> loadInitial(int pageSize);
+ public abstract void loadInitial(@Nullable Key initialLoadKey, int requestedLoadSize,
+ boolean enablePlaceholders, @NonNull InitialLoadCallback<Value> callback);
/**
* Load list data after the specified 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 the number loaded than reduce.
+ * <p>
+ * Data may be passed synchronously during the loadAfter method, or deferred and called at a
+ * later time. Further loads going down will be blocked until the callback is called.
+ * <p>
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and prevent further loading.
*
* @param currentEndKey Load items after this key. May be null on initial load, to indicate load
* from beginning.
- * @param pageSize Suggested number of items to load.
- * @return List of items, starting after the specified item. Null if the data source is
- * no longer valid, and should not be queried again.
+ * @param pageSize Suggested number of items to load.
+ * @param callback DataSource.LoadCallback that receives loaded data.
*/
- @SuppressWarnings("WeakerAccess")
- @WorkerThread
- @Nullable
- public abstract List<Value> loadAfter(@NonNull Key currentEndKey, int pageSize);
+ public abstract void loadAfter(@NonNull Key currentEndKey, int pageSize,
+ @NonNull LoadCallback<Value> callback);
/**
- * Load data before the currently loaded content, starting at the provided index,
- * in reverse-display order.
+ * Load data before the currently loaded content.
* <p>
* It's valid to return a different list size than the page size, if it's easier for this data
- * source. It is generally safer to increase the number loaded than reduce.
- * <p class="note"><strong>Note:</strong> Items returned from loadBefore <em>must</em> be in
- * reverse order from how they will be presented in the list. The first item in the return list
- * will be prepended immediately before the current beginning of the list. This is so that the
- * KeyedDataSource may return a different number of items from the requested {@code pageSize} by
- * shortening or lengthening the return list as it desires.
+ * source. It is generally safer to increase the number loaded than reduce. Note that the last
+ * item returned must be directly adjacent to the key passed, so varying size from the pageSize
+ * requested should effectively grow or shrink the list by modifying the beginning, not the end.
* <p>
+ * Data may be passed synchronously during the loadBefore method, or deferred and called at a
+ * later time. Further loads going up will be blocked until the callback is called.
+ * <p>
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and prevent further loading.
+ * <p class="note"><strong>Note:</strong> Data must be returned in the order it will be
+ * presented in the list.
*
* @param currentBeginKey Load items before this key.
- * @param pageSize Suggested number of items to load.
- * @return List of items, in descending order, starting after the specified item. Null if the
- * data source is no longer valid, and should not be queried again.
+ * @param pageSize Suggested number of items to load.
+ * @param callback DataSource.LoadCallback that receives loaded data.
*/
- @SuppressWarnings("WeakerAccess")
- @WorkerThread
- @Nullable
- public abstract List<Value> loadBefore(@NonNull Key currentBeginKey, int pageSize);
+ public abstract void loadBefore(@NonNull Key currentBeginKey, int pageSize,
+ @NonNull LoadCallback<Value> callback);
- @Nullable
- @Override
- Key getKey(int position, Value item) {
- if (item == null) {
- return null;
- }
- return getKey(item);
- }
+ /**
+ * Return a key associated with the given item.
+ * <p>
+ * If your KeyedDataSource is loading from a source that is sorted and loaded by a unique
+ * integer ID, you would return {@code item.getID()} here. This key can then be passed to
+ * {@link #loadBefore(Object, int, LoadCallback)} or
+ * {@link #loadAfter(Object, int, LoadCallback)} to load additional items adjacent to the item
+ * passed to this function.
+ * <p>
+ * If your key is more complex, such as when you're sorting by name, then resolving collisions
+ * with integer ID, you'll need to return both. In such a case you would use a wrapper class,
+ * such as {@code Pair<String, Integer>} or, in Kotlin,
+ * {@code data class Key(val name: String, val id: Int)}
+ *
+ * @param item Item to get the key from.
+ * @return Key associated with given item.
+ */
+ @NonNull
+ public abstract Key getKey(@NonNull Value item);
}
diff --git a/android/arch/paging/ListDataSource.java b/android/arch/paging/ListDataSource.java
index d3a171e5..b6f366a3 100644
--- a/android/arch/paging/ListDataSource.java
+++ b/android/arch/paging/ListDataSource.java
@@ -16,10 +16,12 @@
package android.arch.paging;
+import android.support.annotation.NonNull;
+
import java.util.ArrayList;
import java.util.List;
-public class ListDataSource<T> extends TiledDataSource<T> {
+class ListDataSource<T> extends PositionalDataSource<T> {
private final List<T> mList;
public ListDataSource(List<T> list) {
@@ -27,13 +29,22 @@ public class ListDataSource<T> extends TiledDataSource<T> {
}
@Override
- public int countItems() {
- return mList.size();
+ public void loadInitial(int requestedStartPosition, int requestedLoadSize, int pageSize,
+ @NonNull InitialLoadCallback<T> callback) {
+ final int totalCount = mList.size();
+
+ final int firstLoadPosition = computeFirstLoadPosition(
+ requestedStartPosition, requestedLoadSize, pageSize, totalCount);
+ final int firstLoadSize = Math.min(totalCount - firstLoadPosition, requestedLoadSize);
+
+ // for simplicity, we could return everything immediately,
+ // but we tile here since it's expected behavior
+ List<T> sublist = mList.subList(firstLoadPosition, firstLoadPosition + firstLoadSize);
+ callback.onResult(sublist, firstLoadPosition, totalCount);
}
@Override
- public List<T> loadRange(int startPosition, int count) {
- int endExclusive = Math.min(mList.size(), startPosition + count);
- return mList.subList(startPosition, endExclusive);
+ public void loadRange(int startPosition, int count, @NonNull LoadCallback<T> callback) {
+ callback.onResult(mList.subList(startPosition, startPosition + count));
}
}
diff --git a/android/arch/paging/LivePagedListBuilder.java b/android/arch/paging/LivePagedListBuilder.java
index ee1810b5..b0fddba2 100644
--- a/android/arch/paging/LivePagedListBuilder.java
+++ b/android/arch/paging/LivePagedListBuilder.java
@@ -25,42 +25,76 @@ import android.support.annotation.Nullable;
import java.util.concurrent.Executor;
+/**
+ * Builder for {@code LiveData<PagedList>}, given a {@link DataSource.Factory} and a
+ * {@link PagedList.Config}.
+ * <p>
+ * The required parameters are in the constructor, so you can simply construct and build, or
+ * optionally enable extra features (such as initial load key, or BoundaryCallback.
+ *
+ * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if
+ * you're using PositionalDataSource.
+ * @param <Value> Item type being presented.
+ */
public class LivePagedListBuilder<Key, Value> {
private Key mInitialLoadKey;
private PagedList.Config mConfig;
private DataSource.Factory<Key, Value> mDataSourceFactory;
private PagedList.BoundaryCallback mBoundaryCallback;
- private Executor mMainThreadExecutor;
private Executor mBackgroundThreadExecutor;
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public LivePagedListBuilder<Key, Value> setInitialLoadKey(@Nullable Key key) {
- mInitialLoadKey = key;
- return this;
- }
-
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public LivePagedListBuilder<Key, Value> setPagingConfig(@NonNull PagedList.Config config) {
+ /**
+ * Creates a LivePagedListBuilder with required parameters.
+ *
+ * @param dataSourceFactory DataSource factory providing DataSource generations.
+ * @param config Paging configuration.
+ */
+ public LivePagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory,
+ @NonNull PagedList.Config config) {
+ mDataSourceFactory = dataSourceFactory;
mConfig = config;
- return this;
}
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public LivePagedListBuilder<Key, Value> setPagingConfig(int pageSize) {
- mConfig = new PagedList.Config.Builder().setPageSize(pageSize).build();
- return this;
+ /**
+ * Creates a LivePagedListBuilder with required parameters.
+ * <p>
+ * This method is a convenience for:
+ * <pre>
+ * LivePagedListBuilder(dataSourceFactory,
+ * new PagedList.Config.Builder().setPageSize(pageSize).build())
+ * </pre>
+ *
+ * @param dataSourceFactory DataSource.Factory providing DataSource generations.
+ * @param pageSize Size of pages to load.
+ */
+ public LivePagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory,
+ int pageSize) {
+ this(dataSourceFactory, new PagedList.Config.Builder().setPageSize(pageSize).build());
}
+ /**
+ * First loading key passed to the first PagedList/DataSource.
+ * <p>
+ * When a new PagedList/DataSource pair is created after the first, it acquires a load key from
+ * the previous generation so that data is loaded around the position already being observed.
+ *
+ * @param key Initial load key passed to the first PagedList/DataSource.
+ * @return this
+ */
@NonNull
- public LivePagedListBuilder<Key, Value> setDataSourceFactory(
- @NonNull DataSource.Factory<Key, Value> dataSourceFactory) {
- mDataSourceFactory = dataSourceFactory;
+ public LivePagedListBuilder<Key, Value> setInitialLoadKey(@Nullable Key key) {
+ mInitialLoadKey = key;
return this;
}
+ /**
+ * Sets a {@link PagedList.BoundaryCallback} on each PagedList created.
+ * <p>
+ * This can be used to
+ *
+ * @param boundaryCallback The boundary callback for listening to PagedList load state.
+ * @return this
+ */
@SuppressWarnings("unused")
@NonNull
public LivePagedListBuilder<Key, Value> setBoundaryCallback(
@@ -69,14 +103,15 @@ public class LivePagedListBuilder<Key, Value> {
return this;
}
- @SuppressWarnings("unused")
- @NonNull
- public LivePagedListBuilder<Key, Value> setMainThreadExecutor(
- @NonNull Executor mainThreadExecutor) {
- mMainThreadExecutor = mainThreadExecutor;
- return this;
- }
-
+ /**
+ * Sets executor which will be used for background loading of pages.
+ * <p>
+ * Does not affect initial load, which will be always be done on done on the Arch components
+ * I/O thread.
+ *
+ * @param backgroundThreadExecutor Executor for background DataSource loading.
+ * @return this
+ */
@SuppressWarnings("unused")
@NonNull
public LivePagedListBuilder<Key, Value> setBackgroundThreadExecutor(
@@ -85,6 +120,14 @@ public class LivePagedListBuilder<Key, Value> {
return this;
}
+ /**
+ * Constructs the {@code LiveData<PagedList>}.
+ * <p>
+ * No work (such as loading) is done immediately, the creation of the first PagedList is is
+ * deferred until the LiveData is observed.
+ *
+ * @return The LiveData of PagedLists
+ */
@NonNull
public LiveData<PagedList<Value>> build() {
if (mConfig == null) {
@@ -93,20 +136,17 @@ public class LivePagedListBuilder<Key, Value> {
if (mDataSourceFactory == null) {
throw new IllegalArgumentException("DataSource.Factory must be provided");
}
- if (mMainThreadExecutor == null) {
- mMainThreadExecutor = ArchTaskExecutor.getMainThreadExecutor();
- }
if (mBackgroundThreadExecutor == null) {
mBackgroundThreadExecutor = ArchTaskExecutor.getIOThreadExecutor();
}
return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
- mMainThreadExecutor, mBackgroundThreadExecutor);
+ ArchTaskExecutor.getMainThreadExecutor(), mBackgroundThreadExecutor);
}
@AnyThread
@NonNull
- public static <Key, Value> LiveData<PagedList<Value>> create(
+ private static <Key, Value> LiveData<PagedList<Value>> create(
@Nullable final Key initialLoadKey,
@NonNull final PagedList.Config config,
@Nullable final PagedList.BoundaryCallback boundaryCallback,
@@ -143,12 +183,10 @@ public class LivePagedListBuilder<Key, Value> {
mDataSource = dataSourceFactory.create();
mDataSource.addInvalidatedCallback(mCallback);
- mList = new PagedList.Builder<Key, Value>()
- .setDataSource(mDataSource)
+ mList = new PagedList.Builder<>(mDataSource, config)
.setMainThreadExecutor(mainThreadExecutor)
.setBackgroundThreadExecutor(backgroundThreadExecutor)
.setBoundaryCallback(boundaryCallback)
- .setConfig(config)
.setInitialKey(initializeKey)
.build();
} while (mList.isDetached());
diff --git a/android/arch/paging/Page.java b/android/arch/paging/Page.java
deleted file mode 100644
index e9890ed4..00000000
--- a/android/arch/paging/Page.java
+++ /dev/null
@@ -1,51 +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 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/PageResult.java b/android/arch/paging/PageResult.java
index 55d5fb76..cf2216fa 100644
--- a/android/arch/paging/PageResult.java
+++ b/android/arch/paging/PageResult.java
@@ -16,11 +16,31 @@
package android.arch.paging;
-import android.support.annotation.AnyThread;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.support.annotation.IntDef;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
-class PageResult<K, V> {
+import java.lang.annotation.Retention;
+import java.util.Collections;
+import java.util.List;
+
+class PageResult<T> {
+ @SuppressWarnings("unchecked")
+ private static final PageResult INVALID_RESULT =
+ new PageResult(Collections.EMPTY_LIST, 0);
+
+ @SuppressWarnings("unchecked")
+ static <T> PageResult<T> getInvalidResult() {
+ return INVALID_RESULT;
+ }
+
+
+ @Retention(SOURCE)
+ @IntDef({INIT, APPEND, PREPEND, TILE})
+ @interface ResultType {}
+
static final int INIT = 0;
// contiguous results
@@ -30,8 +50,8 @@ class PageResult<K, V> {
// non-contiguous, tile result
static final int TILE = 3;
- public final int type;
- public final Page<K, V> page;
+ @NonNull
+ public final List<T> page;
@SuppressWarnings("WeakerAccess")
public final int leadingNulls;
@SuppressWarnings("WeakerAccess")
@@ -39,26 +59,34 @@ class PageResult<K, V> {
@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;
+ PageResult(@NonNull List<T> list, int leadingNulls, int trailingNulls, int positionOffset) {
+ this.page = list;
this.leadingNulls = leadingNulls;
this.trailingNulls = trailingNulls;
this.positionOffset = positionOffset;
}
- PageResult(int type) {
- this.type = type;
- this.page = null;
+ PageResult(@NonNull List<T> list, int positionOffset) {
+ this.page = list;
this.leadingNulls = 0;
this.trailingNulls = 0;
- this.positionOffset = 0;
+ this.positionOffset = positionOffset;
+ }
+
+ @Override
+ public String toString() {
+ return "Result " + leadingNulls
+ + ", " + page
+ + ", " + trailingNulls
+ + ", offset " + positionOffset;
+ }
+
+ public boolean isInvalid() {
+ return this == INVALID_RESULT;
}
- interface Receiver<K, V> {
- @AnyThread
- void postOnPageResult(@NonNull PageResult<K, V> pageResult);
+ abstract static class Receiver<T> {
@MainThread
- void onPageResult(@NonNull PageResult<K, V> pageResult);
+ public abstract void onPageResult(@ResultType int type, @NonNull PageResult<T> pageResult);
}
}
diff --git a/android/arch/paging/PagedList.java b/android/arch/paging/PagedList.java
index f18e108c..4e17a151 100644
--- a/android/arch/paging/PagedList.java
+++ b/android/arch/paging/PagedList.java
@@ -17,6 +17,7 @@
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;
@@ -51,7 +52,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* PagedList can present data for an unbounded, infinite scrolling list, or a very large but
* countable list. Use {@link Config} to control how many items a PagedList loads, and when.
* <p>
- * If you use {@link LivePagedListProvider} to get a
+ * If you use {@link LivePagedListBuilder} to get a
* {@link android.arch.lifecycle.LiveData}&lt;PagedList>, it will initialize PagedLists on a
* background thread for you.
* <h4>Placeholders</h4>
@@ -88,9 +89,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
* </ul>
* <p>
* Placeholders are enabled by default, but can be disabled in two ways. They are disabled if the
- * DataSource returns {@link DataSource#COUNT_UNDEFINED} from any item counting method, or if
- * {@code false} is passed to {@link Config.Builder#setEnablePlaceholders(boolean)} when building a
- * {@link Config}.
+ * DataSource does not count its data set in its initial load, or if {@code false} is passed to
+ * {@link Config.Builder#setEnablePlaceholders(boolean)} when building a {@link Config}.
*
* @param <T> The type of the entries in the list.
*/
@@ -104,7 +104,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
@NonNull
final Config mConfig;
@NonNull
- final PagedStorage<?, T> mStorage;
+ final PagedStorage<T> mStorage;
int mLastLoad = 0;
T mLastItem = null;
@@ -123,7 +123,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
- PagedList(@NonNull PagedStorage<?, T> storage,
+ PagedList(@NonNull PagedStorage<T> storage,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<T> boundaryCallback,
@@ -162,7 +162,8 @@ public abstract class PagedList<T> extends AbstractList<T> {
if (dataSource.isContiguous() || !config.enablePlaceholders) {
if (!dataSource.isContiguous()) {
//noinspection unchecked
- dataSource = (DataSource<K, T>) ((TiledDataSource<T>) dataSource).getAsContiguous();
+ dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
+ .wrapAsContiguousWithoutPlaceholders();
}
ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
return new ContiguousPagedList<>(contigDataSource,
@@ -172,7 +173,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
config,
key);
} else {
- return new TiledPagedList<>((TiledDataSource<T>) dataSource,
+ return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
mainThreadExecutor,
backgroundThreadExecutor,
boundaryCallback,
@@ -184,18 +185,14 @@ public abstract class PagedList<T> extends AbstractList<T> {
/**
* Builder class for PagedList.
* <p>
- * DataSource, main thread and background executor, and Config must all be provided.
+ * DataSource, Config, main thread and background executor must all be provided.
* <p>
- * A valid PagedList may not be constructed without data, so building a PagedList queries
- * initial data from the data source. This is done because it's generally undesired to present a
- * PagedList with no data in it to the UI. It's better to present initial data, so that the UI
- * doesn't show an empty list, or placeholders for a few frames, just before showing initial
- * content.
+ * A PagedList queries initial data from its DataSource during construction, to avoid empty
+ * PagedLists being presented to the UI when possible. It's preferred to present initial data,
+ * so that the UI doesn't show an empty list, or placeholders for a few frames, just before
+ * showing initial content.
* <p>
- * Because PagedLists are initialized with data, PagedLists must be built on a background
- * thread.
- * <p>
- * {@link LivePagedListProvider} does this creation on a background thread automatically, if you
+ * {@link LivePagedListBuilder} does this creation on a background thread automatically, if you
* want to receive a {@code LiveData<PagedList<...>>}.
*
* @param <Key> Type of key used to load data from the DataSource.
@@ -203,26 +200,48 @@ public abstract class PagedList<T> extends AbstractList<T> {
*/
@SuppressWarnings("WeakerAccess")
public static class Builder<Key, Value> {
- private DataSource<Key, Value> mDataSource;
+ private final DataSource<Key, Value> mDataSource;
+ private final Config mConfig;
private Executor mMainThreadExecutor;
private Executor mBackgroundThreadExecutor;
private BoundaryCallback mBoundaryCallback;
- private Config mConfig;
private Key mInitialKey;
/**
- * The source of data that the PagedList should load from.
- * @param dataSource Source of data for the PagedList.
+ * Create a PagedList.Builder with the provided {@link DataSource} and {@link Config}.
*
- * @return this
+ * @param dataSource DataSource the PagedList will load from.
+ * @param config Config that defines how the PagedList loads data from its DataSource.
*/
- @NonNull
- public Builder<Key, Value> setDataSource(@NonNull DataSource<Key, Value> dataSource) {
+ public Builder(@NonNull DataSource<Key, Value> dataSource, @NonNull Config config) {
+ //noinspection ConstantConditions
+ if (dataSource == null) {
+ throw new IllegalArgumentException("DataSource may not be null");
+ }
+ //noinspection ConstantConditions
+ if (config == null) {
+ throw new IllegalArgumentException("Config may not be null");
+ }
mDataSource = dataSource;
- return this;
+ mConfig = config;
}
/**
+ * Create a PagedList.Builder with the provided {@link DataSource} and page size.
+ * <p>
+ * This method is a convenience for:
+ * <pre>
+ * PagedList.Builder(dataSource,
+ * new PagedList.Config.Builder().setPageSize(pageSize).build());
+ * </pre>
+ *
+ * @param dataSource DataSource the PagedList will load from.
+ * @param pageSize Config that defines how the PagedList loads data from its DataSource.
+ */
+ public Builder(@NonNull DataSource<Key, Value> dataSource, int pageSize) {
+ this(dataSource, new PagedList.Config.Builder().setPageSize(pageSize).build());
+ }
+ /**
* The executor defining where main/UI thread for page loading updates.
*
* @param mainThreadExecutor Executor for main/UI thread to receive {@link Callback} calls.
@@ -250,24 +269,19 @@ public abstract class PagedList<T> extends AbstractList<T> {
return this;
}
- @NonNull
- public Builder<Key, Value> setBoundaryCallback(
- @Nullable BoundaryCallback boundaryCallback) {
- mBoundaryCallback = boundaryCallback;
- return this;
- }
-
-
/**
- * The Config defining how the PagedList should load from the DataSource.
- *
- * @param config The config that will define how the PagedList loads from the DataSource.
+ * The BoundaryCallback for out of data events.
+ * <p>
+ * Pass a BoundaryCallback to listen to when the PagedList runs out of data to load.
*
+ * @param boundaryCallback BoundaryCallback for listening to out-of-data events.
* @return this
*/
+ @SuppressWarnings("unused")
@NonNull
- public Builder<Key, Value> setConfig(@NonNull Config config) {
- mConfig = config;
+ public Builder<Key, Value> setBoundaryCallback(
+ @Nullable BoundaryCallback boundaryCallback) {
+ mBoundaryCallback = boundaryCallback;
return this;
}
@@ -286,8 +300,21 @@ public abstract class PagedList<T> extends AbstractList<T> {
/**
* Creates a {@link PagedList} with the given parameters.
* <p>
- * This call will initial data and perform any counting needed to initialize the PagedList,
- * therefore it should only be called on a worker thread.
+ * This call will dispatch the {@link DataSource}'s loadInitial method immediately. If a
+ * DataSource posts all of its work (e.g. to a network thread), the PagedList will
+ * be immediately created as empty, and grow to its initial size when the initial load
+ * completes.
+ * <p>
+ * If the DataSource implements its load synchronously, doing the load work immediately in
+ * the loadInitial method, the PagedList will block on that load before completing
+ * construction. In this case, use a background thread to create a PagedList.
+ * <p>
+ * It's fine to create a PagedList with an async DataSource on the main thread, such as in
+ * the constructor of a ViewModel. An async network load won't block the initialLoad
+ * function. For a synchronous DataSource such as one created from a Room database, a
+ * {@code LiveData<PagedList>} can be safely constructed with {@link LivePagedListBuilder}
+ * on the main thread, since actual construction work is deferred, and done on a background
+ * thread.
* <p>
* While build() will always return a PagedList, it's important to note that the PagedList
* initial load may fail to acquire data from the DataSource. This can happen for example if
@@ -300,18 +327,13 @@ public abstract class PagedList<T> extends AbstractList<T> {
@WorkerThread
@NonNull
public PagedList<Value> build() {
- if (mDataSource == null) {
- throw new IllegalArgumentException("DataSource required");
- }
+ // TODO: define defaults, once they can be used in module without android dependency
if (mMainThreadExecutor == null) {
throw new IllegalArgumentException("MainThreadExecutor required");
}
if (mBackgroundThreadExecutor == null) {
throw new IllegalArgumentException("BackgroundThreadExecutor required");
}
- if (mConfig == null) {
- throw new IllegalArgumentException("Config required");
- }
//noinspection unchecked
return PagedList.create(
@@ -451,13 +473,11 @@ public abstract class PagedList<T> extends AbstractList<T> {
// safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present
if (begin) {
//noinspection ConstantConditions
- mBoundaryCallback.onItemAtFrontLoaded(
- snapshot(), mStorage.getFirstLoadedItem(), mStorage.size());
+ mBoundaryCallback.onItemAtFrontLoaded(mStorage.getFirstLoadedItem());
}
if (end) {
//noinspection ConstantConditions
- mBoundaryCallback.onItemAtEndLoaded(
- snapshot(), mStorage.getLastLoadedItem(), mStorage.size());
+ mBoundaryCallback.onItemAtEndLoaded(mStorage.getLastLoadedItem());
}
}
@@ -501,7 +521,6 @@ public abstract class PagedList<T> extends AbstractList<T> {
if (isImmutable()) {
return this;
}
-
return new SnapshotPagedList<>(this);
}
@@ -522,7 +541,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
* <p>
* When a PagedList is invalidated, you can pass the key returned by this function to initialize
* the next PagedList. This ensures (depending on load times) that the next PagedList that
- * arrives will have data that overlaps. If you use {@link LivePagedListProvider}, it will do
+ * arrives will have data that overlaps. If you use {@link LivePagedListBuilder}, it will do
* this for you.
*
* @return Key of position most recently passed to {@link #loadAround(int)}.
@@ -556,8 +575,8 @@ public abstract class PagedList<T> extends AbstractList<T> {
/**
* Position offset of the data in the list.
* <p>
- * If data is supplied by a {@link TiledDataSource}, the item returned from <code>get(i)</code>
- * has a position of <code>i + getPositionOffset()</code>.
+ * If data is supplied by a {@link PositionalDataSource}, the item returned from
+ * <code>get(i)</code> has a position of <code>i + getPositionOffset()</code>.
* <p>
* If the DataSource is a {@link KeyedDataSource}, and thus doesn't use positions, returns 0.
*/
@@ -583,15 +602,25 @@ public abstract class PagedList<T> extends AbstractList<T> {
* GC'd.
*
* @param previousSnapshot Snapshot previously captured from this List, or null.
- * @param callback Callback to dispatch to.
+ * @param callback LoadCallback to dispatch to.
* @see #removeWeakCallback(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);
+
+ if (previousSnapshot.isEmpty()) {
+ if (!mStorage.isEmpty()) {
+ // If snapshot is empty, diff is trivial - just notify number new items.
+ // Note: occurs in async init, when snapshot taken before init page arrives
+ callback.onInserted(0, mStorage.size());
+ }
+ } else {
+ PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot;
+
+ //noinspection unchecked
+ dispatchUpdatesSinceSnapshot(storageSnapshot, callback);
+ }
}
// first, clean up any empty weak refs
@@ -608,7 +637,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
/**
* Removes a previously added callback.
*
- * @param callback Callback, previously added.
+ * @param callback LoadCallback, previously added.
* @see #addWeakCallback(List, Callback)
*/
@SuppressWarnings("WeakerAccess")
@@ -645,6 +674,14 @@ public abstract class PagedList<T> extends AbstractList<T> {
}
}
+
+
+ /**
+ * Dispatch updates since the non-empty snapshot was taken.
+ *
+ * @param snapshot Non-empty snapshot.
+ * @param callback LoadCallback for updates that have occurred since snapshot.
+ */
abstract void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> snapshot,
@NonNull Callback callback);
@@ -826,13 +863,6 @@ public abstract class PagedList<T> extends AbstractList<T> {
* 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.
*
* @param initialLoadSizeHint Number of items to load while initializing the PagedList.
@@ -873,13 +903,43 @@ public abstract class PagedList<T> extends AbstractList<T> {
}
/**
- * WIP API for load-more-into-local-storage callbacks
+ * Signals when a PagedList has reached the end of available data.
+ * <p>
+ * This can be used to implement paging from the network into a local database - when the
+ * database has no more data to present, a BoundaryCallback can be used to fetch more data.
+ * <p>
+ * If an instance is shared across multiple PagedLists (e.g. when passed to
+ * {@link LivePagedListBuilder#setBoundaryCallback}), the callbacks may be issued multiple
+ * times. If for example {@link #onItemAtEndLoaded(Object)} triggers a network load, it should
+ * avoid triggering it again while the load is ongoing.
+ *
+ * @param <T> Type loaded by the PagedList.
*/
+ @MainThread
public abstract static class BoundaryCallback<T> {
- public abstract void onZeroItemsLoaded();
- public abstract void onItemAtFrontLoaded(@NonNull List<T> pagedListSnapshot,
- @NonNull T itemAtFront, int pagedListSize);
- public abstract void onItemAtEndLoaded(@NonNull List<T> pagedListSnapshot,
- @NonNull T itemAtEnd, int pagedListSize);
+ /**
+ * Called when zero items are returned from an initial load of the PagedList's data source.
+ */
+ public void onZeroItemsLoaded() {}
+
+ /**
+ * Called when the item at the front of the PagedList has been loaded, and access has
+ * occurred within {@link Config#prefetchDistance} of it.
+ * <p>
+ * No more data will be prepended to the PagedList before this item.
+ *
+ * @param itemAtFront The first item of PagedList
+ */
+ public void onItemAtFrontLoaded(@NonNull T itemAtFront) {}
+
+ /**
+ * Called when the item at the end of the PagedList has been loaded, and access has
+ * occurred within {@link Config#prefetchDistance} of it.
+ * <p>
+ * No more data will be appended to the PagedList after this item.
+ *
+ * @param itemAtEnd The first item of PagedList
+ */
+ public void onItemAtEndLoaded(@NonNull T itemAtEnd) {}
}
}
diff --git a/android/arch/paging/PagedListAdapter.java b/android/arch/paging/PagedListAdapter.java
index 89b9c2ee..a8158c23 100644
--- a/android/arch/paging/PagedListAdapter.java
+++ b/android/arch/paging/PagedListAdapter.java
@@ -44,18 +44,14 @@ import android.support.v7.widget.RecyclerView;
* {@literal @}Dao
* interface UserDao {
* {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- * public abstract LivePagedListProvider&lt;Integer, User> usersByLastName();
+ * public abstract DataSource.Factory&lt;Integer, User> usersByLastName();
* }
*
* class MyViewModel extends ViewModel {
* public final LiveData&lt;PagedList&lt;User>> usersList;
* public MyViewModel(UserDao userDao) {
- * usersList = userDao.usersByLastName().create(
- * /* initial load position {@literal *}/ 0,
- * new PagedList.Config.Builder()
- * .setPageSize(50)
- * .setPrefetchDistance(50)
- * .build());
+ * usersList = LivePagedListBuilder&lt;>(
+ * userDao.usersByLastName(), /* page size {@literal *}/ 20).build();
* }
* }
*
diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java
index 51a6e37f..7a0b81a6 100644
--- a/android/arch/paging/PagedListAdapterHelper.java
+++ b/android/arch/paging/PagedListAdapterHelper.java
@@ -48,18 +48,14 @@ import android.support.v7.widget.RecyclerView;
* {@literal @}Dao
* interface UserDao {
* {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- * public abstract LivePagedListProvider&lt;Integer, User> usersByLastName();
+ * public abstract DataSource.Factory&lt;Integer, User> usersByLastName();
* }
*
* class MyViewModel extends ViewModel {
* public final LiveData&lt;PagedList&lt;User>> usersList;
* public MyViewModel(UserDao userDao) {
- * usersList = userDao.usersByLastName().create(
- * /* initial load position {@literal *}/ 0,
- * new PagedList.Config.Builder()
- * .setPageSize(50)
- * .setPrefetchDistance(50)
- * .build());
+ * usersList = LivePagedListBuilder&lt;>(
+ * userDao.usersByLastName(), /* page size {@literal *}/ 20).build();
* }
* }
*
diff --git a/android/arch/paging/PagedStorage.java b/android/arch/paging/PagedStorage.java
index b857462b..d4531d33 100644
--- a/android/arch/paging/PagedStorage.java
+++ b/android/arch/paging/PagedStorage.java
@@ -17,13 +17,21 @@
package android.arch.paging;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import java.util.AbstractList;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
-final class PagedStorage<K, V> extends AbstractList<V> {
+final class PagedStorage<T> extends AbstractList<T> {
+ /**
+ * Lists instances are compared (with instance equality) to PLACEHOLDER_LIST to check if an item
+ * in that position is already loading. We use a singleton placeholder list that is distinct
+ * from Collections.EMPTY_LIST for safety.
+ */
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+ private static final List PLACEHOLDER_LIST = new ArrayList();
+
// Always set
private int mLeadingNullCount;
/**
@@ -37,7 +45,7 @@ final class PagedStorage<K, V> extends AbstractList<V> {
* 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 final ArrayList<List<T>> mPages;
private int mTrailingNullCount;
private int mPositionOffset;
@@ -53,9 +61,6 @@ final class PagedStorage<K, V> extends AbstractList<V> {
private int mNumberPrepended;
private int mNumberAppended;
- // only used in tiling case
- private Page<K, V> mPlaceholderPage;
-
PagedStorage() {
mLeadingNullCount = 0;
mPages = new ArrayList<>();
@@ -67,12 +72,12 @@ final class PagedStorage<K, V> extends AbstractList<V> {
mNumberAppended = 0;
}
- PagedStorage(int leadingNulls, Page<K, V> page, int trailingNulls) {
+ PagedStorage(int leadingNulls, List<T> page, int trailingNulls) {
this();
init(leadingNulls, page, trailingNulls, 0);
}
- private PagedStorage(PagedStorage<K, V> other) {
+ private PagedStorage(PagedStorage<T> other) {
mLeadingNullCount = other.mLeadingNullCount;
mPages = new ArrayList<>(other.mPages);
mTrailingNullCount = other.mTrailingNullCount;
@@ -81,40 +86,37 @@ final class PagedStorage<K, V> extends AbstractList<V> {
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() {
+ PagedStorage<T> snapshot() {
return new PagedStorage<>(this);
}
- private void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset) {
+ private void init(int leadingNulls, List<T> page, int trailingNulls, int positionOffset) {
mLeadingNullCount = leadingNulls;
mPages.clear();
mPages.add(page);
mTrailingNullCount = trailingNulls;
mPositionOffset = positionOffset;
- mStorageCount = page.items.size();
+ mStorageCount = page.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();
+ mPageSize = page.size();
mNumberPrepended = 0;
mNumberAppended = 0;
}
- void init(int leadingNulls, Page<K, V> page, int trailingNulls, int positionOffset,
+ void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
@NonNull Callback callback) {
init(leadingNulls, page, trailingNulls, positionOffset);
callback.onInitialized(size());
}
@Override
- public V get(int i) {
+ public T get(int i) {
if (i < 0 || i >= size()) {
throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size());
}
@@ -138,7 +140,7 @@ final class PagedStorage<K, V> extends AbstractList<V> {
pageInternalIndex = localIndex;
final int localPageCount = mPages.size();
for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) {
- int pageSize = mPages.get(localPageIndex).items.size();
+ int pageSize = mPages.get(localPageIndex).size();
if (pageSize > pageInternalIndex) {
// stop, found the page
break;
@@ -147,12 +149,12 @@ final class PagedStorage<K, V> extends AbstractList<V> {
}
}
- Page<?, V> page = mPages.get(localPageIndex);
- if (page == null || page.items.size() == 0) {
+ List<T> page = mPages.get(localPageIndex);
+ if (page == null || page.size() == 0) {
// can only occur in tiled case, with untouched inner/placeholder pages
return null;
}
- return page.items.get(pageInternalIndex);
+ return page.get(pageInternalIndex);
}
/**
@@ -207,8 +209,8 @@ final class PagedStorage<K, V> extends AbstractList<V> {
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) {
+ List page = mPages.get(i);
+ if (page != null && page != PLACEHOLDER_LIST) {
break;
}
total += mPageSize;
@@ -219,8 +221,8 @@ final class PagedStorage<K, V> extends AbstractList<V> {
int computeTrailingNulls() {
int total = mTrailingNullCount;
for (int i = mPages.size() - 1; i >= 0; i--) {
- Page page = mPages.get(i);
- if (page != null && page != mPlaceholderPage) {
+ List page = mPages.get(i);
+ if (page != null && page != PLACEHOLDER_LIST) {
break;
}
total += mPageSize;
@@ -230,21 +232,21 @@ final class PagedStorage<K, V> extends AbstractList<V> {
// ---------------- Contiguous API -------------------
- V getFirstLoadedItem() {
+ T getFirstLoadedItem() {
// 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);
+ return mPages.get(0).get(0);
}
- V getLastLoadedItem() {
+ T getLastLoadedItem() {
// 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);
+ List<T> page = mPages.get(mPages.size() - 1);
+ return page.get(page.size() - 1);
}
- public void prependPage(@NonNull Page<K, V> page, @NonNull Callback callback) {
- final int count = page.items.size();
+ void prependPage(@NonNull List<T> page, @NonNull Callback callback) {
+ final int count = page.size();
if (count == 0) {
// Nothing returned from source, stop loading in this direction
return;
@@ -274,8 +276,8 @@ final class PagedStorage<K, V> extends AbstractList<V> {
callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount);
}
- public void appendPage(@NonNull Page<K, V> page, @NonNull Callback callback) {
- final int count = page.items.size();
+ void appendPage(@NonNull List<T> page, @NonNull Callback callback) {
+ final int count = page.size();
if (count == 0) {
// Nothing returned from source, stop loading in this direction
return;
@@ -284,7 +286,7 @@ final class PagedStorage<K, V> extends AbstractList<V> {
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
+ if (mPages.get(mPages.size() - 1).size() != mPageSize
|| count > mPageSize) {
mPageSize = -1;
}
@@ -306,8 +308,30 @@ final class PagedStorage<K, V> extends AbstractList<V> {
// ------------------ Non-Contiguous API (tiling required) ----------------------
- public void insertPage(int position, @NonNull Page<K, V> page, Callback callback) {
- final int newPageSize = page.items.size();
+ void initAndSplit(int leadingNulls, @NonNull List<T> multiPageList,
+ int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) {
+
+ int pageCount = (multiPageList.size() + (pageSize - 1)) / pageSize;
+ for (int i = 0; i < pageCount; i++) {
+ int beginInclusive = i * pageSize;
+ int endExclusive = Math.min(multiPageList.size(), (i + 1) * pageSize);
+
+ List<T> sublist = multiPageList.subList(beginInclusive, endExclusive);
+
+ if (i == 0) {
+ // Trailing nulls for first page includes other pages in multiPageList
+ int initialTrailingNulls = trailingNulls + multiPageList.size() - sublist.size();
+ init(leadingNulls, sublist, initialTrailingNulls, positionOffset);
+ } else {
+ int insertPosition = leadingNulls + beginInclusive;
+ insertPage(insertPosition, sublist, null);
+ }
+ }
+ callback.onInitialized(size());
+ }
+
+ public void insertPage(int position, @NonNull List<T> page, @Nullable Callback callback) {
+ final int newPageSize = page.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)
@@ -334,22 +358,15 @@ final class PagedStorage<K, V> extends AbstractList<V> {
int localPageIndex = pageIndex - mLeadingNullCount / mPageSize;
- Page<K, V> oldPage = mPages.get(localPageIndex);
- if (oldPage != null && oldPage != mPlaceholderPage) {
+ List<T> oldPage = mPages.get(localPageIndex);
+ if (oldPage != null && oldPage != PLACEHOLDER_LIST) {
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);
+ if (callback != null) {
+ callback.onPageInserted(position, page.size());
}
- return mPlaceholderPage;
}
private void allocatePageRange(final int minimumPage, final int maximumPage) {
@@ -399,7 +416,8 @@ final class PagedStorage<K, V> extends AbstractList<V> {
for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) {
int localPageIndex = pageIndex - leadingNullPages;
if (mPages.get(localPageIndex) == null) {
- mPages.set(localPageIndex, getPlaceholderPage());
+ //noinspection unchecked
+ mPages.set(localPageIndex, PLACEHOLDER_LIST);
callback.onPagePlaceholderInserted(pageIndex);
}
}
@@ -414,9 +432,9 @@ final class PagedStorage<K, V> extends AbstractList<V> {
return false;
}
- Page<K, V> page = mPages.get(index - leadingNullPages);
+ List<T> page = mPages.get(index - leadingNullPages);
- return page != null && page != mPlaceholderPage;
+ return page != null && page != PLACEHOLDER_LIST;
}
@Override
diff --git a/android/arch/paging/PagedStorageDiffHelper.java b/android/arch/paging/PagedStorageDiffHelper.java
index 6fc70390..d991b723 100644
--- a/android/arch/paging/PagedStorageDiffHelper.java
+++ b/android/arch/paging/PagedStorageDiffHelper.java
@@ -26,8 +26,8 @@ class PagedStorageDiffHelper {
}
static <T> DiffUtil.DiffResult computeDiff(
- final PagedStorage<?, T> oldList,
- final PagedStorage<?, T> newList,
+ final PagedStorage<T> oldList,
+ final PagedStorage<T> newList,
final DiffCallback<T> diffCallback) {
final int oldOffset = oldList.computeLeadingNulls();
final int newOffset = newList.computeLeadingNulls();
@@ -131,8 +131,8 @@ class PagedStorageDiffHelper {
* immediately after dispatching this diff.
*/
static <T> void dispatchDiff(ListUpdateCallback callback,
- final PagedStorage<?, T> oldList,
- final PagedStorage<?, T> newList,
+ final PagedStorage<T> oldList,
+ final PagedStorage<T> newList,
final DiffUtil.DiffResult diffResult) {
final int trailingOld = oldList.computeTrailingNulls();
diff --git a/android/arch/paging/PositionalDataSource.java b/android/arch/paging/PositionalDataSource.java
index fa2932ad..d3946370 100644
--- a/android/arch/paging/PositionalDataSource.java
+++ b/android/arch/paging/PositionalDataSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,115 +20,284 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
+import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Executor;
/**
- * Incremental data loader for paging positional content, where content can be loaded based on its
- * integer position.
+ * Position-based data loader for a fixed-size, countable data set, supporting loads at arbitrary
+ * positions.
* <p>
- * Use PositionalDataSource if you only need position as input for item loading - if for example,
- * you're asking the backend for items at positions 10 through 20, or using a limit/offset database
- * query to load items at query position 10 through 20.
+ * Extend PositionalDataSource if you can support counting your data set, and loading based on
+ * position information.
* <p>
- * Implement a DataSource using PositionalDataSource if position is the only information you need to
- * load items.
+ * Note that unless {@link PagedList.Config#enablePlaceholders placeholders are disabled}
+ * PositionalDataSource requires counting the size of the dataset. This allows pages to be tiled in
+ * at arbitrary, non-contiguous locations based upon what the user observes in a {@link PagedList}.
* <p>
- * Note that {@link BoundedDataSource} provides a simpler API for positional loading, if your
- * backend or data store doesn't require
- * <p>
- * @param <Value> Value type of items being loaded by the DataSource.
+ * Room can generate a Factory of PositionalDataSources for you:
+ * <pre>
+ * {@literal @}Dao
+ * interface UserDao {
+ * {@literal @}Query("SELECT * FROM user ORDER BY mAge DESC")
+ * public abstract DataSource.Factory&lt;Integer, User> loadUsersByAgeDesc();
+ * }</pre>
+ *
+ * @param <T> Type of items being loaded by the PositionalDataSource.
*/
-abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> {
-
+public abstract class PositionalDataSource<T> extends DataSource<Integer, T> {
/**
- * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED.
+ * Callback for PositionalDataSource initial loading methods to return data, position, and
+ * (optionally) count information.
+ * <p>
+ * A callback can be called only once, and will throw if called again.
+ * <p>
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
*
- * @return number of items that this DataSource can provide in total, or COUNT_UNDEFINED
- * if difficult or undesired to compute.
+ * @param <T> Type of items being loaded.
*/
- public int countItems() {
- return COUNT_UNDEFINED;
- }
+ public static class InitialLoadCallback<T> extends BaseLoadCallback<T> {
+ private final boolean mCountingEnabled;
+ private final int mPageSize;
- @Nullable
- @Override
- List<Value> loadAfterImpl(int currentEndIndex, @NonNull Value currentEndItem, int pageSize) {
- return loadAfter(currentEndIndex + 1, pageSize);
- }
+ InitialLoadCallback(@NonNull PositionalDataSource dataSource, boolean countingEnabled,
+ int pageSize, PageResult.Receiver<T> receiver) {
+ super(PageResult.INIT, dataSource, null, receiver);
+ mCountingEnabled = countingEnabled;
+ mPageSize = pageSize;
+ if (mPageSize < 1) {
+ throw new IllegalArgumentException("Page size must be non-negative");
+ }
+ }
- @Nullable
- @Override
- List<Value> loadBeforeImpl(int currentBeginIndex, @NonNull Value currentBeginItem,
- int pageSize) {
- return loadBefore(currentBeginIndex - 1, pageSize);
- }
+ /**
+ * Called to pass initial load state from a DataSource.
+ * <p>
+ * Call this method from your DataSource's {@code loadInitial} function to return data,
+ * and inform how many placeholders should be shown before and after. If counting is cheap
+ * to compute (for example, if a network load returns the information regardless), it's
+ * recommended to pass data back through this method.
+ *
+ * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+ * is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are {@code N}
+ * items before the items in data that can be loaded from this DataSource,
+ * pass {@code N}.
+ * @param totalCount Total number of items that may be returned from this DataSource.
+ * Includes the number in the initial {@code data} parameter
+ * as well as any items that can be loaded in front or behind of
+ * {@code data}.
+ */
+ public void onResult(@NonNull List<T> data, int position, int totalCount) {
+ validateInitialLoadParams(data, position, totalCount);
+ if (position + data.size() != totalCount
+ && data.size() % mPageSize != 0) {
+ throw new IllegalArgumentException("PositionalDataSource requires initial load size"
+ + " to be a multiple of page size to support internal tiling.");
+ }
- @Override
- void loadInitial(Integer position, int initialLoadSize, boolean enablePlaceholders,
- @NonNull PageResult.Receiver<Integer, Value> receiver) {
+ if (mCountingEnabled) {
+ int trailingUnloadedCount = totalCount - position - data.size();
+ dispatchResultToReceiver(
+ new PageResult<>(data, position, trailingUnloadedCount, 0));
+ } else {
+ // Only occurs when wrapped as contiguous
+ dispatchResultToReceiver(new PageResult<>(data, position));
+ }
+ }
- final int convertPosition = position == null ? 0 : position;
- final int loadPosition = Math.max(0, (convertPosition - initialLoadSize / 2));
+ /**
+ * Called to pass initial load state from a DataSource without supporting placeholders.
+ * <p>
+ * Call this method from your DataSource's {@code loadInitial} function to return data,
+ * if position is known but total size is not. If counting is not expensive, consider
+ * calling the three parameter variant: {@link #onResult(List, int, int)}.
+ *
+ * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+ * is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are {@code N}
+ * items before the items in data that can be provided by this DataSource,
+ * pass {@code N}.
+ */
+ void onResult(@NonNull List<T> data, int position) {
+ // not counting, don't need to check mAcceptCount
+ dispatchResultToReceiver(new PageResult<>(
+ data, 0, 0, position));
+ }
+ }
- int count = COUNT_UNDEFINED;
- if (enablePlaceholders) {
- count = countItems();
+ /**
+ * Callback for PositionalDataSource {@link #loadRange(int, int, LoadCallback)} methods
+ * to return data.
+ * <p>
+ * A callback can be called only once, and will throw if called again.
+ * <p>
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param <T> Type of items being loaded.
+ */
+ public static class LoadCallback<T> extends BaseLoadCallback<T> {
+ private final int mPositionOffset;
+ LoadCallback(@NonNull PositionalDataSource dataSource, int positionOffset,
+ Executor mainThreadExecutor, PageResult.Receiver<T> receiver) {
+ super(PageResult.TILE, dataSource, mainThreadExecutor, receiver);
+ mPositionOffset = positionOffset;
}
- List<Value> data = loadAfter(loadPosition, initialLoadSize);
- if (data == null) {
- receiver.onPageResult(new PageResult<Integer, Value>(PageResult.INIT));
- return;
+ /**
+ * Called to pass loaded data from a DataSource.
+ * <p>
+ * Call this method from your DataSource's {@code load} methods to return data.
+ *
+ * @param data List of items loaded from the DataSource.
+ */
+ public void onResult(@NonNull List<T> data) {
+ dispatchResultToReceiver(new PageResult<>(
+ data, 0, 0, mPositionOffset));
}
+ }
+
+ void loadInitial(boolean acceptCount,
+ int requestedStartPosition, int requestedLoadSize, int pageSize,
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+ InitialLoadCallback<T> callback =
+ new InitialLoadCallback<>(this, acceptCount, pageSize, receiver);
+ loadInitial(requestedStartPosition, requestedLoadSize, pageSize, callback);
- final boolean uncounted = count == COUNT_UNDEFINED;
- int leadingNullCount = uncounted ? 0 : loadPosition;
- int trailingNullCount = uncounted ? 0 : count - leadingNullCount - data.size();
- int positionOffset = uncounted ? loadPosition : 0;
-
- receiver.onPageResult(new PageResult<>(
- PageResult.INIT,
- new Page<Integer, Value>(data),
- leadingNullCount,
- trailingNullCount,
- positionOffset));
+ // If initialLoad's callback is not called within the body, we force any following calls
+ // to post to the UI thread. This constructor may be run on a background thread, but
+ // after constructor, mutation must happen on UI thread.
+ callback.setPostExecutor(mainThreadExecutor);
+ }
+
+ void loadRange(int startPosition, int count,
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+ LoadCallback<T> callback =
+ new LoadCallback<>(this, startPosition, mainThreadExecutor, receiver);
+ if (count == 0) {
+ callback.onResult(Collections.<T>emptyList());
+ } else {
+ loadRange(startPosition, count, callback);
+ }
}
/**
- * Load data after currently loaded content, starting at the provided index.
+ * Load initial list data.
* <p>
- * It's valid to return a different list size than the page size, if it's easier for this data
- * source. It is generally safer to increase the number loaded than reduce.
+ * This method is called to load the initial page(s) from the DataSource.
+ * <p>
+ * Result list must be a multiple of pageSize to enable efficient tiling.
*
- * @param startIndex Load items starting at this index.
- * @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.
+ * @param requestedStartPosition Initial load position requested. Note that this may not be
+ * within the bounds of your data set, it should be corrected
+ * before you make your query.
+ * @param requestedLoadSize Requested number of items to load. Note that this may be larger than
+ * available data.
+ * @param pageSize Defines page size acceptable for return values. List of items passed to the
+ * callback must be an integer multiple of page size.
+ * @param callback DataSource.InitialLoadCallback that receives initial load data, including
+ * position and total data set size.
*/
@WorkerThread
- @Nullable
- public abstract List<Value> loadAfter(int startIndex, int pageSize);
+ public abstract void loadInitial(int requestedStartPosition, int requestedLoadSize,
+ int pageSize, @NonNull InitialLoadCallback<T> callback);
/**
- * Load data before the currently loaded content, starting at the provided index.
+ * Called to load a range of data from the DataSource.
+ * <p>
+ * This method is called to load additional pages from the DataSource after the
+ * InitialLoadCallback passed to loadInitial has initialized a PagedList.
* <p>
- * It's valid to return a different list size than the page size, if it's easier for this data
- * source. It is generally safer to increase the number loaded than reduce.
+ * Unlike {@link #loadInitial(int, int, int, InitialLoadCallback)}, this method must return the
+ * number of items requested, at the position requested.
*
- * @param startIndex Load items, starting at this index.
- * @param pageSize Suggested number of items to load.
- * @return List of items, in descending order, starting at position currentBeginIndex - 1. Null
- * if the data source is no longer valid, and should not be queried again.
+ * @param startPosition Initial load position.
+ * @param count Number of items to load.
+ * @param callback DataSource.LoadCallback that receives loaded data.
*/
@WorkerThread
- @Nullable
- public abstract List<Value> loadBefore(int startIndex, int pageSize);
+ public abstract void loadRange(int startPosition, int count, @NonNull LoadCallback<T> callback);
@Override
- Integer getKey(int position, Value item) {
- if (position < 0) {
- return null;
+ boolean isContiguous() {
+ return false;
+ }
+
+
+ @NonNull
+ ContiguousDataSource<Integer, T> wrapAsContiguousWithoutPlaceholders() {
+ return new ContiguousWithoutPlaceholdersWrapper<>(this);
+ }
+
+ static int computeFirstLoadPosition(int position, int firstLoadSize, int pageSize, int size) {
+ int roundedPageStart = Math.round(position / pageSize) * pageSize;
+
+ // maximum start pos is that which will encompass end of list
+ int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize;
+ roundedPageStart = Math.min(maximumLoadPage, roundedPageStart);
+
+ // minimum start position is 0
+ roundedPageStart = Math.max(0, roundedPageStart);
+
+ return roundedPageStart;
+ }
+
+ @SuppressWarnings("deprecation")
+ static class ContiguousWithoutPlaceholdersWrapper<Value>
+ extends ContiguousDataSource<Integer, Value> {
+
+ @NonNull
+ final PositionalDataSource<Value> mPositionalDataSource;
+
+ ContiguousWithoutPlaceholdersWrapper(
+ @NonNull PositionalDataSource<Value> positionalDataSource) {
+ mPositionalDataSource = positionalDataSource;
+ }
+
+ @Override
+ void loadInitial(@Nullable Integer position, int initialLoadSize, int pageSize,
+ boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
+ @NonNull PageResult.Receiver<Value> receiver) {
+ final int convertPosition = position == null ? 0 : position;
+
+ // Note enablePlaceholders will be false here, but we don't have a way to communicate
+ // this to PositionalDataSource. This is fine, because only the list and its position
+ // offset will be consumed by the InitialLoadCallback.
+ mPositionalDataSource.loadInitial(false, convertPosition, initialLoadSize,
+ pageSize, mainThreadExecutor, receiver);
+ }
+
+ @Override
+ void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
+ @NonNull Executor mainThreadExecutor,
+ @NonNull PageResult.Receiver<Value> receiver) {
+ int startIndex = currentEndIndex + 1;
+ mPositionalDataSource.loadRange(startIndex, pageSize, mainThreadExecutor, receiver);
+ }
+
+ @Override
+ void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
+ @NonNull Executor mainThreadExecutor,
+ @NonNull PageResult.Receiver<Value> receiver) {
+
+ int startIndex = currentBeginIndex - 1;
+ if (startIndex < 0) {
+ // trigger empty list load
+ mPositionalDataSource.loadRange(startIndex, 0, mainThreadExecutor, receiver);
+ } else {
+ int loadSize = Math.min(pageSize, startIndex + 1);
+ startIndex = startIndex - loadSize + 1;
+ mPositionalDataSource.loadRange(startIndex, loadSize, mainThreadExecutor, receiver);
+ }
+ }
+
+ @Override
+ Integer getKey(int position, Value item) {
+ return position;
}
- return position;
}
}
diff --git a/android/arch/paging/TiledDataSource.java b/android/arch/paging/TiledDataSource.java
index 0ea94286..34d0091e 100644
--- a/android/arch/paging/TiledDataSource.java
+++ b/android/arch/paging/TiledDataSource.java
@@ -16,84 +16,24 @@
package android.arch.paging;
-import android.support.annotation.Nullable;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
import java.util.Collections;
import java.util.List;
/**
- * Position-based data loader for fixed size, arbitrary positioned loading.
- * <p>
- * Extend TiledDataSource if you want to load arbitrary pages based solely on position information,
- * and can generate pages of a provided fixed size.
- * <p>
- * Room can generate a TiledDataSource for you:
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- * {@literal @}Query("SELECT * FROM user ORDER BY mAge DESC")
- * public abstract TiledDataSource&lt;User> loadUsersByAgeDesc();
- * }</pre>
+ * @param <T> Type loaded by the TiledDataSource.
*
- * Under the hood, Room will generate code equivalent to the below, using a limit/offset SQL query:
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- * {@literal @}Query("SELECT COUNT(*) from user")
- * public abstract Integer getUserCount();
- *
- * {@literal @}Query("SELECT * from user ORDER BY mName DESC LIMIT :limit OFFSET :offset")
- * public abstract List&lt;User> userNameLimitOffset(int limit, int offset);
- * }
- *
- * public class OffsetUserQueryDataSource extends TiledDataSource&lt;User> {
- * private MyDatabase mDb;
- * private final UserDao mUserDao;
- * {@literal @}SuppressWarnings("FieldCanBeLocal")
- * private final InvalidationTracker.Observer mObserver;
- *
- * public OffsetUserQueryDataSource(MyDatabase db) {
- * mDb = db;
- * mUserDao = db.getUserDao();
- * mObserver = new InvalidationTracker.Observer("user") {
- * {@literal @}Override
- * public void onInvalidated({@literal @}NonNull Set&lt;String> tables) {
- * // the user table has been invalidated, invalidate the DataSource
- * invalidate();
- * }
- * };
- * db.getInvalidationTracker().addWeakObserver(mObserver);
- * }
- *
- * {@literal @}Override
- * public boolean isInvalid() {
- * mDb.getInvalidationTracker().refreshVersionsSync();
- * return super.isInvalid();
- * }
- *
- * {@literal @}Override
- * public int countItems() {
- * return mUserDao.getUserCount();
- * }
- *
- * {@literal @}Override
- * public List&lt;User> loadRange(int startPosition, int loadCount) {
- * return mUserDao.userNameLimitOffset(loadCount, startPosition);
- * }
- * }</pre>
- *
- * @param <Type> Type of items being loaded by the TiledDataSource.
+ * @deprecated Use {@link PositionalDataSource}
+ * @hide
*/
-public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
+@SuppressWarnings("DeprecatedIsStillUsed")
+@Deprecated
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class TiledDataSource<T> extends PositionalDataSource<T> {
- private int mItemCount;
-
- /**
- * Number of items that this DataSource can provide in total.
- *
- * @return Number of items this DataSource can provide. Must be <code>0</code> or greater.
- */
@WorkerThread
public abstract int countItems();
@@ -102,111 +42,39 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
return false;
}
- /**
- * Called to load items at from the specified position range.
- * <p>
- * This method must return a list of requested size, unless at the end of list. Fixed size pages
- * enable TiledDataSource to navigate tiles efficiently, and quickly accesss any position in the
- * data set.
- * <p>
- * If a list of a different size is returned, but it is not the last list in the data set based
- * on the return value from {@link #countItems()}, an exception will be thrown.
- *
- * @param startPosition Index of first item to load.
- * @param count Number of items to load.
- * @return List of loaded items, of the requested length unless at end of list. Null if the
- * DataSource is no longer valid, and should not be queried again.
- */
@WorkerThread
- public abstract List<Type> loadRange(int startPosition, int count);
-
- /**
- * blocking, and splits pages
- */
- void loadRangeInitial(int startPosition, int count, int pageSize, int itemCount,
- PageResult.Receiver<Integer, Type> receiver) {
- mItemCount = itemCount;
+ public abstract List<T> loadRange(int startPosition, int count);
- 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));
+ @Override
+ public void loadInitial(int requestedStartPosition, int requestedLoadSize, int pageSize,
+ @NonNull InitialLoadCallback callback) {
+ int totalCount = countItems();
+ if (totalCount == 0) {
+ callback.onResult(Collections.<T>emptyList(), 0, 0);
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 = null;
- int trailingNulls = mItemCount - startPosition;
+ // bound the size requested, based on known count
+ final int firstLoadPosition = computeFirstLoadPosition(
+ requestedStartPosition, requestedLoadSize, pageSize, totalCount);
+ final int firstLoadSize = Math.min(totalCount - firstLoadPosition, requestedLoadSize);
+ // convert from legacy behavior
+ List<T> list = loadRange(firstLoadPosition, firstLoadSize);
if (list != null) {
- page = new Page<Integer, Type>(list);
- trailingNulls -= list.size();
+ callback.onResult(list, firstLoadPosition, totalCount);
+ } else {
+ invalidate();
}
- receiver.postOnPageResult(new PageResult<>(
- PageResult.TILE, page, startPosition, trailingNulls, 0));
}
- private List<Type> loadRangeWrapper(int startPosition, int count) {
- if (isInvalid()) {
- return null;
- }
- List<Type> list = loadRange(startPosition, count);
- if (isInvalid()) {
- return null;
- }
- return list;
- }
-
- ContiguousDataSource<Integer, Type> getAsContiguous() {
- return new TiledAsBoundedDataSource<>(this);
- }
-
- static class TiledAsBoundedDataSource<Value> extends BoundedDataSource<Value> {
- final TiledDataSource<Value> mTiledDataSource;
-
- TiledAsBoundedDataSource(TiledDataSource<Value> tiledDataSource) {
- mTiledDataSource = tiledDataSource;
- }
-
- @WorkerThread
- @Nullable
- @Override
- public List<Value> loadRange(int startPosition, int loadCount) {
- return mTiledDataSource.loadRange(startPosition, loadCount);
+ @Override
+ public void loadRange(int startPosition, int count, @NonNull LoadCallback callback) {
+ List<T> list = loadRange(startPosition, count);
+ if (list != null) {
+ callback.onResult(list);
+ } else {
+ invalidate();
}
}
}
diff --git a/android/arch/paging/TiledPagedList.java b/android/arch/paging/TiledPagedList.java
index 76bb682d..6c189cdb 100644
--- a/android/arch/paging/TiledPagedList.java
+++ b/android/arch/paging/TiledPagedList.java
@@ -25,32 +25,16 @@ import java.util.concurrent.Executor;
class TiledPagedList<T> extends PagedList<T>
implements PagedStorage.Callback {
+ private final PositionalDataSource<T> mDataSource;
- private final TiledDataSource<T> mDataSource;
-
- @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 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 PageResult.Receiver<T> mReceiver = new PageResult.Receiver<T>() {
// Creation thread for initial synchronous load, otherwise main thread
// Safe to access main thread only state - no other thread has reference during construction
@AnyThread
@Override
- public void onPageResult(@NonNull PageResult<Integer, T> pageResult) {
- if (pageResult.page == null) {
+ public void onPageResult(@PageResult.ResultType int type,
+ @NonNull PageResult<T> pageResult) {
+ if (pageResult.isInvalid()) {
detach();
return;
}
@@ -61,60 +45,56 @@ class TiledPagedList<T> extends PagedList<T>
}
if (mStorage.getPageCount() == 0) {
- mKeyedStorage.init(
+ mStorage.initAndSplit(
pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls,
- pageResult.positionOffset, TiledPagedList.this);
+ pageResult.positionOffset, mConfig.pageSize, TiledPagedList.this);
} else {
- mKeyedStorage.insertPage(pageResult.leadingNulls, pageResult.page,
+ mStorage.insertPage(pageResult.positionOffset, pageResult.page,
TiledPagedList.this);
}
if (mBoundaryCallback != null) {
boolean deferEmpty = mStorage.size() == 0;
- boolean deferBegin = !deferEmpty && pageResult.leadingNulls == 0;
- boolean deferEnd = !deferEmpty && pageResult.trailingNulls == 0;
+ boolean deferBegin = !deferEmpty
+ && pageResult.leadingNulls == 0
+ && pageResult.positionOffset == 0;
+ int size = size();
+ boolean deferEnd = !deferEmpty
+ && ((type == PageResult.INIT && pageResult.trailingNulls == 0)
+ || (type == PageResult.TILE
+ && pageResult.positionOffset
+ == (size - size % mConfig.pageSize)));
deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
}
}
};
@WorkerThread
- TiledPagedList(@NonNull TiledDataSource<T> dataSource,
+ TiledPagedList(@NonNull PositionalDataSource<T> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<T> boundaryCallback,
@NonNull Config config,
int position) {
- super(new PagedStorage<Integer, T>(), mainThreadExecutor, backgroundThreadExecutor,
+ super(new PagedStorage<T>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
final int pageSize = mConfig.pageSize;
+ mLastLoad = position;
- final int itemCount = mDataSource.countItems();
-
- final int firstLoadSize = Math.min(itemCount,
- (Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize);
- final int firstLoadPosition = computeFirstLoadPosition(
- position, firstLoadSize, pageSize, itemCount);
-
- mDataSource.loadRangeInitial(firstLoadPosition, firstLoadSize, pageSize,
- itemCount, mReceiver);
- }
-
- static int computeFirstLoadPosition(int position, int firstLoadSize, int pageSize, int size) {
- int idealStart = position - firstLoadSize / 2;
-
- int roundedPageStart = Math.round(idealStart / pageSize) * pageSize;
+ if (mDataSource.isInvalid()) {
+ detach();
+ } else {
+ final int firstLoadSize =
+ (Math.max(Math.round(mConfig.initialLoadSizeHint / pageSize), 2)) * pageSize;
- // minimum start position is 0
- roundedPageStart = Math.max(0, roundedPageStart);
+ final int idealStart = position - firstLoadSize / 2;
+ final int roundedPageStart = Math.max(0, Math.round(idealStart / pageSize) * pageSize);
- // maximum start pos is that which will encompass end of list
- int maximumLoadPage = ((size - firstLoadSize + pageSize - 1) / pageSize) * pageSize;
- roundedPageStart = Math.min(maximumLoadPage, roundedPageStart);
-
- return roundedPageStart;
+ mDataSource.loadInitial(true, roundedPageStart, firstLoadSize,
+ pageSize, mMainThreadExecutor, mReceiver);
+ }
}
@Override
@@ -132,7 +112,13 @@ class TiledPagedList<T> extends PagedList<T>
protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot,
@NonNull Callback callback) {
//noinspection UnnecessaryLocalVariable
- final PagedStorage<?, T> snapshot = pagedListSnapshot.mStorage;
+ final PagedStorage<T> snapshot = pagedListSnapshot.mStorage;
+
+ if (snapshot.isEmpty()
+ || mStorage.size() != snapshot.size()) {
+ throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
+ + " to be a snapshot of this PagedList");
+ }
// loop through each page and signal the callback for any pages that are present now,
// but not in the snapshot.
@@ -186,7 +172,14 @@ class TiledPagedList<T> extends PagedList<T>
return;
}
final int pageSize = mConfig.pageSize;
- mDataSource.loadRange(pageIndex * pageSize, pageSize, mReceiver);
+
+ if (mDataSource.isInvalid()) {
+ detach();
+ } else {
+ int startPosition = pageIndex * pageSize;
+ int count = Math.min(pageSize, mStorage.size() - startPosition);
+ mDataSource.loadRange(startPosition, count, mMainThreadExecutor, mReceiver);
+ }
}
});
}
diff --git a/android/arch/paging/integration/testapp/ItemDataSource.java b/android/arch/paging/integration/testapp/ItemDataSource.java
index 46905334..bbbfabb8 100644
--- a/android/arch/paging/integration/testapp/ItemDataSource.java
+++ b/android/arch/paging/integration/testapp/ItemDataSource.java
@@ -16,9 +16,10 @@
package android.arch.paging.integration.testapp;
-import android.arch.paging.BoundedDataSource;
+import android.arch.paging.PositionalDataSource;
import android.graphics.Color;
import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
@@ -26,7 +27,7 @@ import java.util.List;
/**
* Sample data source with artificial data.
*/
-class ItemDataSource extends BoundedDataSource<Item> {
+class ItemDataSource extends PositionalDataSource<Item> {
private static final int COUNT = 500;
@ColorInt
@@ -39,18 +40,7 @@ class ItemDataSource extends BoundedDataSource<Item> {
private static int sGenerationId;
private final int mGenerationId = sGenerationId++;
- @Override
- public int countItems() {
- return COUNT;
- }
-
- @Override
- public List<Item> loadRange(int startPosition, int loadCount) {
- if (isInvalid()) {
- // abort!
- return null;
- }
-
+ private List<Item> loadRangeInternal(int startPosition, int loadCount) {
List<Item> items = new ArrayList<>();
int end = Math.min(COUNT, startPosition + loadCount);
int bgColor = COLORS[mGenerationId % COLORS.length];
@@ -63,11 +53,38 @@ class ItemDataSource extends BoundedDataSource<Item> {
for (int i = startPosition; i != end; i++) {
items.add(new Item(i, "item " + i, bgColor));
}
-
- if (isInvalid()) {
- // abort!
- return null;
- }
return items;
}
+
+ // TODO: open up this API in PositionalDataSource?
+ private static int computeFirstLoadPosition(int position, int firstLoadSize,
+ int pageSize, int size) {
+ int roundedPageStart = Math.round(position / pageSize) * pageSize;
+
+ // 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);
+
+ return roundedPageStart;
+ }
+
+ @Override
+ public void loadInitial(int requestedStartPosition, int requestedLoadSize,
+ int pageSize, @NonNull InitialLoadCallback<Item> callback) {
+ requestedStartPosition = computeFirstLoadPosition(
+ requestedStartPosition, requestedLoadSize, pageSize, COUNT);
+
+ requestedLoadSize = Math.min(COUNT - requestedStartPosition, requestedLoadSize);
+ List<Item> data = loadRangeInternal(requestedStartPosition, requestedLoadSize);
+ callback.onResult(data, requestedStartPosition, COUNT);
+ }
+
+ @Override
+ public void loadRange(int startPosition, int count, @NonNull LoadCallback<Item> callback) {
+ List<Item> data = loadRangeInternal(startPosition, count);
+ callback.onResult(data);
+ }
}
diff --git a/android/arch/paging/integration/testapp/PagedListItemViewModel.java b/android/arch/paging/integration/testapp/PagedListItemViewModel.java
index 974eab95..be14bc1b 100644
--- a/android/arch/paging/integration/testapp/PagedListItemViewModel.java
+++ b/android/arch/paging/integration/testapp/PagedListItemViewModel.java
@@ -27,10 +27,24 @@ import android.arch.paging.PagedList;
*/
@SuppressWarnings("WeakerAccess")
public class PagedListItemViewModel extends ViewModel {
- private LiveData<PagedList<Item>> mLivePagedList;
private ItemDataSource mDataSource;
private final Object mDataSourceLock = new Object();
+ private final DataSource.Factory<Integer, Item> mFactory =
+ new DataSource.Factory<Integer, Item>() {
+ @Override
+ public DataSource<Integer, Item> create() {
+ ItemDataSource newDataSource = new ItemDataSource();
+ synchronized (mDataSourceLock) {
+ mDataSource = newDataSource;
+ return mDataSource;
+ }
+ }
+ };
+
+ private LiveData<PagedList<Item>> mLivePagedList =
+ new LivePagedListBuilder<>(mFactory, 20).build();
+
void invalidateList() {
synchronized (mDataSourceLock) {
if (mDataSource != null) {
@@ -40,22 +54,6 @@ public class PagedListItemViewModel extends ViewModel {
}
LiveData<PagedList<Item>> getLivePagedList() {
- if (mLivePagedList == null) {
- mLivePagedList = new LivePagedListBuilder<Integer, Item>()
- .setPagingConfig(20)
- .setDataSourceFactory(new DataSource.Factory<Integer, Item>() {
- @Override
- public DataSource<Integer, Item> create() {
- ItemDataSource newDataSource = new ItemDataSource();
- synchronized (mDataSourceLock) {
- mDataSource = newDataSource;
- return mDataSource;
- }
- }
- })
- .build();
- }
-
return mLivePagedList;
}
}
diff --git a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
index 89d16b7e..d8cd8d49 100644
--- a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
+++ b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
@@ -83,13 +83,12 @@ public class CustomerViewModel extends AndroidViewModel {
private static <K> LiveData<PagedList<Customer>> getLivePagedList(
K initialLoadKey, DataSource.Factory<K, Customer> dataSourceFactory) {
- return new LivePagedListBuilder<K, Customer>()
+ PagedList.Config config = new PagedList.Config.Builder()
+ .setPageSize(10)
+ .setEnablePlaceholders(false)
+ .build();
+ return new LivePagedListBuilder<>(dataSourceFactory, config)
.setInitialLoadKey(initialLoadKey)
- .setPagingConfig(new PagedList.Config.Builder()
- .setPageSize(10)
- .setEnablePlaceholders(false)
- .build())
- .setDataSourceFactory(dataSourceFactory)
.build();
}
diff --git a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
index cdd464e4..0c79aee4 100644
--- a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
+++ b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
@@ -16,7 +16,6 @@
package android.arch.persistence.room.integration.testapp;
-import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
@@ -75,7 +74,7 @@ public class RoomPagedListActivity extends AppCompatActivity {
mAdapter.setList(items);
}
});
- final Button button = findViewById(R.id.button);
+ final Button button = findViewById(R.id.addButton);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -114,11 +113,4 @@ public class RoomPagedListActivity extends AppCompatActivity {
protected boolean useKeyedQuery() {
return false;
}
-
- private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
-
- @Override
- public LifecycleRegistry getLifecycle() {
- return mLifecycleRegistry;
- }
}
diff --git a/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
index cb2bb034..0b184a9c 100644
--- a/android/arch/persistence/room/integration/testapp/dao/UserDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
@@ -190,8 +190,12 @@ public abstract class UserDao {
@Query("SELECT * FROM user where mAge > :age")
public abstract LivePagedListProvider<Integer, User> loadPagedByAge_legacy(int age);
+ // TODO: switch to PositionalDataSource once Room supports it
@Query("SELECT * FROM user ORDER BY mAge DESC")
- public abstract TiledDataSource<User> loadUsersByAgeDesc();
+ public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc();
+
+ @Query("SELECT * FROM user ORDER BY mAge DESC")
+ public abstract TiledDataSource<User> loadUsersByAgeDesc_legacy();
@Query("DELETE FROM User WHERE mId IN (:ids) AND mAge == :age")
public abstract int deleteByAgeAndIds(int age, List<Integer> ids);
diff --git a/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java b/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
index a38d6aed..2db543b3 100644
--- a/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
+++ b/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
@@ -21,6 +21,7 @@ import android.arch.persistence.room.InvalidationTracker;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -60,7 +61,6 @@ public class LastNameAscCustomerDataSource extends KeyedDataSource<String, Custo
@Override
public boolean isInvalid() {
mDb.getInvalidationTracker().refreshVersionsSync();
-
return super.isInvalid();
}
@@ -76,30 +76,48 @@ public class LastNameAscCustomerDataSource extends KeyedDataSource<String, Custo
}
@Override
- public int countItemsBefore(@NonNull String customerName) {
- return mCustomerDao.customerNameCountBefore(customerName);
- }
+ public void loadInitial(@Nullable String customerName, int initialLoadSize,
+ boolean enablePlaceholders, @NonNull InitialLoadCallback<Customer> callback) {
+ List<Customer> list;
+ if (customerName != null) {
+ // initial keyed load - load before 'customerName',
+ // and load after last item in before list
+ int pageSize = initialLoadSize / 2;
+ String key = customerName;
+ list = mCustomerDao.customerNameLoadBefore(key, pageSize);
+ Collections.reverse(list);
+ if (!list.isEmpty()) {
+ key = getKey(list.get(list.size() - 1));
+ }
+ list.addAll(mCustomerDao.customerNameLoadAfter(key, pageSize));
+ } else {
+ list = mCustomerDao.customerNameInitial(initialLoadSize);
+ }
- @Override
- public int countItemsAfter(@NonNull String customerName) {
- return mCustomerDao.customerNameCountAfter(customerName);
- }
+ if (enablePlaceholders && !list.isEmpty()) {
+ String firstKey = getKey(list.get(0));
+ String lastKey = getKey(list.get(list.size() - 1));
- @Nullable
- @Override
- public List<Customer> loadInitial(int pageSize) {
- return mCustomerDao.customerNameInitial(pageSize);
+ // only bother counting if placeholders are desired
+ final int position = mCustomerDao.customerNameCountBefore(firstKey);
+ final int count = position + list.size() + mCustomerDao.customerNameCountAfter(lastKey);
+ callback.onResult(list, position, count);
+ } else {
+ callback.onResult(list);
+ }
}
- @Nullable
@Override
- public List<Customer> loadBefore(@NonNull String customerName, int pageSize) {
- return mCustomerDao.customerNameLoadBefore(customerName, pageSize);
+ public void loadAfter(@NonNull String currentEndKey, int pageSize,
+ @NonNull LoadCallback<Customer> callback) {
+ callback.onResult(mCustomerDao.customerNameLoadAfter(currentEndKey, pageSize));
}
- @Nullable
@Override
- public List<Customer> loadAfter(@Nullable String customerName, int pageSize) {
- return mCustomerDao.customerNameLoadAfter(customerName, pageSize);
+ public void loadBefore(@NonNull String currentBeginKey, int pageSize,
+ @NonNull LoadCallback<Customer> callback) {
+ List<Customer> list = mCustomerDao.customerNameLoadBefore(currentBeginKey, pageSize);
+ Collections.reverse(list);
+ callback.onResult(list);
}
}
diff --git a/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java b/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
index c5465314..b54abe8e 100644
--- a/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
+++ b/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
@@ -63,12 +63,12 @@ public class DataSourceFactoryTest extends TestDatabaseTest {
validateUsersAsPagedList(new LivePagedListFactory() {
@Override
public LiveData<PagedList<User>> create() {
- return new LivePagedListBuilder<Integer, User>()
- .setPagingConfig(new PagedList.Config.Builder()
+ return new LivePagedListBuilder<>(
+ mUserDao.loadPagedByAge(3),
+ new PagedList.Config.Builder()
.setPageSize(10)
.setPrefetchDistance(1)
.setInitialLoadSizeHint(10).build())
- .setDataSourceFactory(mUserDao.loadPagedByAge(3))
.build();
}
});
diff --git a/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java b/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java
index 8226759a..f0285a04 100644
--- a/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java
+++ b/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java
@@ -45,8 +45,17 @@ public class LimitOffsetDataSourceTest extends TestDatabaseTest {
mUserDao.deleteEverything();
}
+ // TODO: delete this and factory abstraction when LivePagedListProvider is removed
+ @Test
+ public void limitOffsetDataSource_legacyTiledDataSource() {
+ // Simple verification that loading a TiledDataSource still works.
+ LimitOffsetDataSource<User> dataSource =
+ (LimitOffsetDataSource<User>) mUserDao.loadUsersByAgeDesc_legacy();
+ assertThat(dataSource.countItems(), is(0));
+ }
+
private LimitOffsetDataSource<User> loadUsersByAgeDesc() {
- return (LimitOffsetDataSource<User>) mUserDao.loadUsersByAgeDesc();
+ return (LimitOffsetDataSource<User>) mUserDao.loadUsersByAgeDesc().create();
}
@Test
diff --git a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
index 291cfd67..f076cf13 100644
--- a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
@@ -203,10 +203,8 @@ public class QueryTransactionTest {
@Test
public void pagedList() {
- LiveData<PagedList<Entity1>> pagedList = new LivePagedListBuilder<Integer, Entity1>()
- .setDataSourceFactory(mDao.pagedList())
- .setPagingConfig(10)
- .build();
+ LiveData<PagedList<Entity1>> pagedList =
+ new LivePagedListBuilder<>(mDao.pagedList(), 10).build();
observeForever(pagedList);
drain();
assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0));
@@ -228,6 +226,7 @@ public class QueryTransactionTest {
mDao.insert(new Entity1(2, "bar"));
drain();
resetTransactionCount();
+ @SuppressWarnings("deprecation")
TiledDataSource<Entity1> dataSource = mDao.dataSource();
dataSource.loadRange(0, 10);
assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0));
diff --git a/android/bluetooth/BluetoothHidDevice.java b/android/bluetooth/BluetoothHidDevice.java
index e3d763ab..6692e137 100644
--- a/android/bluetooth/BluetoothHidDevice.java
+++ b/android/bluetooth/BluetoothHidDevice.java
@@ -350,13 +350,22 @@ public final class BluetoothHidDevice implements BluetoothProfile {
* application can be registered at time. When no longer used, application
* should be unregistered using
* {@link #unregisterApp(BluetoothHidDeviceAppConfiguration)}.
+ * The registration status should be tracked by the application by handling callback from
+ * BluetoothHidDeviceCallback#onAppStatusChanged. The app registration status is not related
+ * to the return value of this method.
*
* @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record.
+ * The HID Device SDP record is required.
* @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings.
+ * The Incoming QoS Settings is not required. Use null or default
+ * BluetoothHidDeviceAppQosSettings.Builder for default values.
* @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings.
+ * The Outgoing QoS Settings is not required. Use null or default
+ * BluetoothHidDeviceAppQosSettings.Builder for default values.
* @param callback {@link BluetoothHidDeviceCallback} object to which callback messages will be
* sent.
- * @return
+ * The BluetoothHidDeviceCallback object is required.
+ * @return true if the command is successfully sent; otherwise false.
*/
public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
@@ -394,12 +403,15 @@ public final class BluetoothHidDevice implements BluetoothProfile {
* {@link #registerApp
* (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
* BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)}
+ * The registration status should be tracked by the application by handling callback from
+ * BluetoothHidDeviceCallback#onAppStatusChanged. The app registration status is not related
+ * to the return value of this method.
*
* @param config {@link BluetoothHidDeviceAppConfiguration} object as obtained from {@link
* BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice,
* BluetoothHidDeviceAppConfiguration,
* boolean)}
- * @return
+ * @return true if the command is successfully sent; otherwise false.
*/
public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) {
Log.v(TAG, "unregisterApp()");
@@ -426,7 +438,7 @@ public final class BluetoothHidDevice implements BluetoothProfile {
* @param id Report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in
* descriptor.
* @param data Report data, not including Report Id.
- * @return
+ * @return true if the command is successfully sent; otherwise false.
*/
public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
boolean result = false;
@@ -452,7 +464,7 @@ public final class BluetoothHidDevice implements BluetoothProfile {
* @param type Report Type, as in request.
* @param id Report Id, as in request.
* @param data Report data, not including Report Id.
- * @return
+ * @return true if the command is successfully sent; otherwise false.
*/
public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
Log.v(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
@@ -478,7 +490,7 @@ public final class BluetoothHidDevice implements BluetoothProfile {
* from {@link BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
*
* @param error Error to be sent for SET_REPORT via HANDSHAKE.
- * @return
+ * @return true if the command is successfully sent; otherwise false.
*/
public boolean reportError(BluetoothDevice device, byte error) {
Log.v(TAG, "reportError(): device=" + device + " error=" + error);
@@ -524,10 +536,13 @@ public final class BluetoothHidDevice implements BluetoothProfile {
}
/**
- * Initiates connection to host which currently has Virtual Cable
- * established with device.
+ * Initiates connection to host which is currently paired with this device.
+ * If the application is not registered, #connect(BluetoothDevice) will fail.
+ * The connection state should be tracked by the application by handling callback from
+ * BluetoothHidDeviceCallback#onConnectionStateChanged. The connection state is not related
+ * to the return value of this method.
*
- * @return
+ * @return true if the command is successfully sent; otherwise false.
*/
public boolean connect(BluetoothDevice device) {
Log.v(TAG, "connect(): device=" + device);
@@ -550,8 +565,11 @@ public final class BluetoothHidDevice implements BluetoothProfile {
/**
* Disconnects from currently connected host.
+ * The connection state should be tracked by the application by handling callback from
+ * BluetoothHidDeviceCallback#onConnectionStateChanged. The connection state is not related
+ * to the return value of this method.
*
- * @return
+ * @return true if the command is successfully sent; otherwise false.
*/
public boolean disconnect(BluetoothDevice device) {
Log.v(TAG, "disconnect(): device=" + device);
diff --git a/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
index ccc3ef40..881ae98d 100644
--- a/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
+++ b/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
@@ -45,6 +45,21 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
public static final int MAX = (int) 0xffffffff;
+ /**
+ * Create a BluetoothHidDeviceAppQosSettings object for the Bluetooth L2CAP channel.
+ * The QoS Settings is optional.
+ * Recommended to use BluetoothHidDeviceAppQosSettings.Builder.
+ * {@see <a href="https://www.bluetooth.com/specifications/profiles-overview">
+ * https://www.bluetooth.com/specifications/profiles-overview
+ * </a>
+ * Bluetooth HID Specfication v1.1.1 Section 5.2 and Appendix D }
+ * @param serviceType L2CAP service type
+ * @param tokenRate L2CAP token rate
+ * @param tokenBucketSize L2CAP token bucket size
+ * @param peakBandwidth L2CAP peak bandwidth
+ * @param latency L2CAP latency
+ * @param delayVariation L2CAP delay variation
+ */
public BluetoothHidDeviceAppQosSettings(int serviceType, int tokenRate, int tokenBucketSize,
int peakBandwidth, int latency, int delayVariation) {
this.serviceType = serviceType;
@@ -59,7 +74,12 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
public boolean equals(Object o) {
if (o instanceof BluetoothHidDeviceAppQosSettings) {
BluetoothHidDeviceAppQosSettings qos = (BluetoothHidDeviceAppQosSettings) o;
- return false;
+ return this.serviceType == qos.serviceType
+ && this.tokenRate == qos.tokenRate
+ && this.tokenBucketSize == qos.tokenBucketSize
+ && this.peakBandwidth == qos.peakBandwidth
+ && this.latency == qos.latency
+ && this.delayVariation == qos.delayVariation;
}
return false;
}
@@ -106,4 +126,85 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation
};
}
+
+ /**
+ * A helper to build the BluetoothHidDeviceAppQosSettings object.
+ */
+ public static class Builder {
+ // Optional parameters - initialized to default values
+ private int mServiceType = SERVICE_BEST_EFFORT;
+ private int mTokenRate = 0;
+ private int mTokenBucketSize = 0;
+ private int mPeakBandwidth = 0;
+ private int mLatency = MAX;
+ private int mDelayVariation = MAX;
+
+ /**
+ * Set the service type.
+ * @param val service type. Should be one of {SERVICE_NO_TRAFFIC, SERVICE_BEST_EFFORT,
+ * SERVICE_GUARANTEED}, with SERVICE_BEST_EFFORT being the default one.
+ * @return BluetoothHidDeviceAppQosSettings Builder with specified service type.
+ */
+ public Builder serviceType(int val) {
+ mServiceType = val;
+ return this;
+ }
+ /**
+ * Set the token rate.
+ * @param val token rate
+ * @return BluetoothHidDeviceAppQosSettings Builder with specified token rate.
+ */
+ public Builder tokenRate(int val) {
+ mTokenRate = val;
+ return this;
+ }
+
+ /**
+ * Set the bucket size.
+ * @param val bucket size
+ * @return BluetoothHidDeviceAppQosSettings Builder with specified bucket size.
+ */
+ public Builder tokenBucketSize(int val) {
+ mTokenBucketSize = val;
+ return this;
+ }
+
+ /**
+ * Set the peak bandwidth.
+ * @param val peak bandwidth
+ * @return BluetoothHidDeviceAppQosSettings Builder with specified peak bandwidth.
+ */
+ public Builder peakBandwidth(int val) {
+ mPeakBandwidth = val;
+ return this;
+ }
+ /**
+ * Set the latency.
+ * @param val latency
+ * @return BluetoothHidDeviceAppQosSettings Builder with specified latency.
+ */
+ public Builder latency(int val) {
+ mLatency = val;
+ return this;
+ }
+
+ /**
+ * Set the delay variation.
+ * @param val delay variation
+ * @return BluetoothHidDeviceAppQosSettings Builder with specified delay variation.
+ */
+ public Builder delayVariation(int val) {
+ mDelayVariation = val;
+ return this;
+ }
+
+ /**
+ * Build the BluetoothHidDeviceAppQosSettings object.
+ * @return BluetoothHidDeviceAppQosSettings object with current settings.
+ */
+ public BluetoothHidDeviceAppQosSettings build() {
+ return new BluetoothHidDeviceAppQosSettings(mServiceType, mTokenRate, mTokenBucketSize,
+ mPeakBandwidth, mLatency, mDelayVariation);
+ }
+ }
}
diff --git a/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
index f01c4932..46696370 100644
--- a/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
+++ b/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
@@ -19,6 +19,8 @@ package android.bluetooth;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Arrays;
+
/**
* Represents the Service Discovery Protocol (SDP) settings for a Bluetooth
* HID Device application.
@@ -39,6 +41,18 @@ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
public final byte subclass;
public final byte[] descriptors;
+ /**
+ * Create a BluetoothHidDeviceAppSdpSettings object for the Bluetooth SDP record.
+ * @param name Name of this Bluetooth HID device. Maximum length is 50 bytes.
+ * @param description Description for this Bluetooth HID device. Maximum length is 50 bytes.
+ * @param provider Provider of this Bluetooth HID device. Maximum length is 50 bytes.
+ * @param subclass Subclass of this Bluetooth HID device.
+ * See <a href="www.usb.org/developers/hidpage/HID1_11.pdf">
+ * www.usb.org/developers/hidpage/HID1_11.pdf Section 4.2</a>
+ * @param descriptors Descriptors of this Bluetooth HID device.
+ * See <a href="www.usb.org/developers/hidpage/HID1_11.pdf">
+ * www.usb.org/developers/hidpage/HID1_11.pdf Chapter 6</a> Maximum length is 2048 bytes.
+ */
public BluetoothHidDeviceAppSdpSettings(String name, String description, String provider,
byte subclass, byte[] descriptors) {
this.name = name;
@@ -52,7 +66,11 @@ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
public boolean equals(Object o) {
if (o instanceof BluetoothHidDeviceAppSdpSettings) {
BluetoothHidDeviceAppSdpSettings sdp = (BluetoothHidDeviceAppSdpSettings) o;
- return false;
+ return this.name.equals(sdp.name)
+ && this.description.equals(sdp.description)
+ && this.provider.equals(sdp.provider)
+ && this.subclass == sdp.subclass
+ && Arrays.equals(this.descriptors, sdp.descriptors);
}
return false;
}
diff --git a/android/content/CursorLoader.java b/android/content/CursorLoader.java
index 33386e5c..7f24c51d 100644
--- a/android/content/CursorLoader.java
+++ b/android/content/CursorLoader.java
@@ -145,7 +145,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
}
/**
- * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
+ * Starts an asynchronous load of the data. When the result is ready the callbacks
* will be called on the UI thread. If a previous load has been completed and is still valid
* the result may be passed to the callbacks immediately.
*
diff --git a/android/content/pm/ActivityInfo.java b/android/content/pm/ActivityInfo.java
index 837c00a7..f8cdce64 100644
--- a/android/content/pm/ActivityInfo.java
+++ b/android/content/pm/ActivityInfo.java
@@ -1156,6 +1156,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
dest.writeString(permission);
dest.writeString(taskAffinity);
dest.writeString(targetActivity);
+ dest.writeString(launchToken);
dest.writeInt(flags);
dest.writeInt(screenOrientation);
dest.writeInt(configChanges);
@@ -1282,6 +1283,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
permission = source.readString();
taskAffinity = source.readString();
targetActivity = source.readString();
+ launchToken = source.readString();
flags = source.readInt();
screenOrientation = source.readInt();
configChanges = source.readInt();
diff --git a/android/content/pm/ApplicationInfo.java b/android/content/pm/ApplicationInfo.java
index 20342807..edb27cd4 100644
--- a/android/content/pm/ApplicationInfo.java
+++ b/android/content/pm/ApplicationInfo.java
@@ -19,6 +19,7 @@ package android.content.pm;
import static android.os.Build.VERSION_CODES.DONUT;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Context;
@@ -890,6 +891,29 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public int versionCode;
/**
+ * The user-visible SDK version (ex. 26) of the framework against which the application claims
+ * to have been compiled, or {@code 0} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#CODENAME Build.VERSION.SDK_INT}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ public int compileSdkVersion;
+
+ /**
+ * The development codename (ex. "O", "REL") of the framework against which the application
+ * claims to have been compiled, or {@code null} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ @Nullable
+ public String compileSdkVersionCodename;
+
+ /**
* When false, indicates that all components within this application are
* considered disabled, regardless of their individually set enabled status.
*/
@@ -1305,6 +1329,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(targetSandboxVersion);
dest.writeString(classLoaderName);
dest.writeStringArray(splitClassLoaderNames);
+ dest.writeInt(compileSdkVersion);
+ dest.writeString(compileSdkVersionCodename);
}
public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -1372,6 +1398,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
targetSandboxVersion = source.readInt();
classLoaderName = source.readString();
splitClassLoaderNames = source.readStringArray();
+ compileSdkVersion = source.readInt();
+ compileSdkVersionCodename = source.readString();
}
/**
diff --git a/android/content/pm/InstantAppInfo.java b/android/content/pm/InstantAppInfo.java
index 67afc928..cb04fc3c 100644
--- a/android/content/pm/InstantAppInfo.java
+++ b/android/content/pm/InstantAppInfo.java
@@ -18,6 +18,7 @@ package android.content.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -31,6 +32,7 @@ import android.os.Parcelable;
*
* @hide
*/
+@SystemApi
public final class InstantAppInfo implements Parcelable {
private final ApplicationInfo mApplicationInfo;
diff --git a/android/content/pm/PackageInfo.java b/android/content/pm/PackageInfo.java
index ba488f6a..f8889b68 100644
--- a/android/content/pm/PackageInfo.java
+++ b/android/content/pm/PackageInfo.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -289,6 +290,29 @@ public class PackageInfo implements Parcelable {
/** @hide */
public boolean isStaticOverlay;
+ /**
+ * The user-visible SDK version (ex. 26) of the framework against which the application claims
+ * to have been compiled, or {@code 0} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ public int compileSdkVersion;
+
+ /**
+ * The development codename (ex. "O", "REL") of the framework against which the application
+ * claims to have been compiled, or {@code null} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ @Nullable
+ public String compileSdkVersionCodename;
+
public PackageInfo() {
}
@@ -344,6 +368,8 @@ public class PackageInfo implements Parcelable {
dest.writeString(overlayTarget);
dest.writeInt(isStaticOverlay ? 1 : 0);
dest.writeInt(overlayPriority);
+ dest.writeInt(compileSdkVersion);
+ dest.writeString(compileSdkVersionCodename);
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -396,6 +422,8 @@ public class PackageInfo implements Parcelable {
overlayTarget = source.readString();
isStaticOverlay = source.readInt() != 0;
overlayPriority = source.readInt();
+ compileSdkVersion = source.readInt();
+ compileSdkVersionCodename = source.readString();
// The component lists were flattened with the redundant ApplicationInfo
// instances omitted. Distribute the canonical one here as appropriate.
diff --git a/android/content/pm/PackageInstaller.java b/android/content/pm/PackageInstaller.java
index 86288396..77c5743f 100644
--- a/android/content/pm/PackageInstaller.java
+++ b/android/content/pm/PackageInstaller.java
@@ -81,6 +81,9 @@ import java.util.List;
* <li>All APKs must have unique split names.
* <li>All installations must contain a single base APK.
* </ul>
+ * <p>
+ * The ApiDemos project contains examples of using this API:
+ * <code>ApiDemos/src/com/example/android/apis/content/InstallApk*.java</code>.
*/
public class PackageInstaller {
private static final String TAG = "PackageInstaller";
diff --git a/android/content/pm/PackageManager.java b/android/content/pm/PackageManager.java
index 31ca1985..f796aabe 100644
--- a/android/content/pm/PackageManager.java
+++ b/android/content/pm/PackageManager.java
@@ -871,8 +871,8 @@ public abstract class PackageManager {
public static final int INSTALL_REASON_USER = 4;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} on success.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * on success.
*
* @hide
*/
@@ -880,8 +880,8 @@ public abstract class PackageManager {
public static final int INSTALL_SUCCEEDED = 1;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the package is already installed.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the package is already installed.
*
* @hide
*/
@@ -889,8 +889,8 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_ALREADY_EXISTS = -1;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the package archive file is invalid.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the package archive file is invalid.
*
* @hide
*/
@@ -898,8 +898,8 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_INVALID_APK = -2;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the URI passed in is invalid.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the URI passed in is invalid.
*
* @hide
*/
@@ -907,9 +907,9 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_INVALID_URI = -3;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the package manager service found that
- * the device didn't have enough storage space to install the app.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the package manager service found that the device didn't have enough storage space to
+ * install the app.
*
* @hide
*/
@@ -917,9 +917,8 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if a package is already installed with
- * the same name.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if a package is already installed with the same name.
*
* @hide
*/
@@ -927,9 +926,8 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the requested shared user does not
- * exist.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the requested shared user does not exist.
*
* @hide
*/
@@ -937,10 +935,9 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_NO_SHARED_USER = -6;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if a previously installed package of the
- * same name has a different signature than the new package (and the old
- * package's data was not removed).
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if a previously installed package of the same name has a different signature than the new
+ * package (and the old package's data was not removed).
*
* @hide
*/
@@ -948,10 +945,9 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package is requested a shared
- * user which is already installed on the device and does not have matching
- * signature.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package is requested a shared user which is already installed on the device and
+ * does not have matching signature.
*
* @hide
*/
@@ -959,9 +955,8 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package uses a shared library
- * that is not available.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package uses a shared library that is not available.
*
* @hide
*/
@@ -969,9 +964,8 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package uses a shared library
- * that is not available.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package uses a shared library that is not available.
*
* @hide
*/
@@ -979,10 +973,9 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package failed while
- * optimizing and validating its dex files, either because there was not
- * enough storage or the validation failed.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package failed while optimizing and validating its dex files, either because there
+ * was not enough storage or the validation failed.
*
* @hide
*/
@@ -990,9 +983,9 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_DEXOPT = -11;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package failed because the
- * current SDK version is older than that required by the package.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package failed because the current SDK version is older than that required by the
+ * package.
*
* @hide
*/
@@ -1000,10 +993,9 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_OLDER_SDK = -12;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package failed because it
- * contains a content provider with the same authority as a provider already
- * installed in the system.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package failed because it contains a content provider with the same authority as a
+ * provider already installed in the system.
*
* @hide
*/
@@ -1011,9 +1003,9 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package failed because the
- * current SDK version is newer than that required by the package.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package failed because the current SDK version is newer than that required by the
+ * package.
*
* @hide
*/
@@ -1021,10 +1013,9 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_NEWER_SDK = -14;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package failed because it has
- * specified that it is a test-only package and the caller has not supplied
- * the {@link #INSTALL_ALLOW_TEST} flag.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package failed because it has specified that it is a test-only package and the
+ * caller has not supplied the {@link #INSTALL_ALLOW_TEST} flag.
*
* @hide
*/
@@ -1032,9 +1023,9 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_TEST_ONLY = -15;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the package being installed contains
- * native code, but none that is compatible with the device's CPU_ABI.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the package being installed contains native code, but none that is compatible with the
+ * device's CPU_ABI.
*
* @hide
*/
@@ -1042,9 +1033,8 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package uses a feature that is
- * not available.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package uses a feature that is not available.
*
* @hide
*/
@@ -1053,9 +1043,9 @@ public abstract class PackageManager {
// ------ Errors related to sdcard
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if a secure container mount point
- * couldn't be accessed on external media.
+ * Installation return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if a secure container mount point couldn't be
+ * accessed on external media.
*
* @hide
*/
@@ -1063,9 +1053,8 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package couldn't be installed
- * in the specified install location.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package couldn't be installed in the specified install location.
*
* @hide
*/
@@ -1073,9 +1062,9 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package couldn't be installed
- * in the specified install location because the media is not available.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package couldn't be installed in the specified install location because the media
+ * is not available.
*
* @hide
*/
@@ -1083,9 +1072,8 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package couldn't be installed
- * because the verification timed out.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package couldn't be installed because the verification timed out.
*
* @hide
*/
@@ -1093,9 +1081,8 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_VERIFICATION_TIMEOUT = -21;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package couldn't be installed
- * because the verification did not succeed.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package couldn't be installed because the verification did not succeed.
*
* @hide
*/
@@ -1103,9 +1090,8 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_VERIFICATION_FAILURE = -22;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the package changed from what the
- * calling program expected.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the package changed from what the calling program expected.
*
* @hide
*/
@@ -1113,28 +1099,25 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_PACKAGE_CHANGED = -23;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package is assigned a
- * different UID than it previously held.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package is assigned a different UID than it previously held.
*
* @hide
*/
public static final int INSTALL_FAILED_UID_CHANGED = -24;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package has an older version
- * code than the currently installed package.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package has an older version code than the currently installed package.
*
* @hide
*/
public static final int INSTALL_FAILED_VERSION_DOWNGRADE = -25;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the old package has target SDK high
- * enough to support runtime permission and the new package has target SDK
- * low enough to not support runtime permissions.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the old package has target SDK high enough to support runtime permission and the new
+ * package has target SDK low enough to not support runtime permissions.
*
* @hide
*/
@@ -1142,9 +1125,8 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE = -26;
/**
- * Installation return code: this is passed to the
- * {@link IPackageInstallObserver} if the new package attempts to downgrade the
- * target sandbox version of the app.
+ * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
+ * if the new package attempts to downgrade the target sandbox version of the app.
*
* @hide
*/
@@ -1152,9 +1134,9 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE = -27;
/**
- * Installation parse return code: this is passed to the
- * {@link IPackageInstallObserver} if the parser was given a path that is
- * not a file, or does not end with the expected '.apk' extension.
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was given a path that is not a
+ * file, or does not end with the expected '.apk' extension.
*
* @hide
*/
@@ -1162,8 +1144,8 @@ public abstract class PackageManager {
public static final int INSTALL_PARSE_FAILED_NOT_APK = -100;
/**
- * Installation parse return code: this is passed to the
- * {@link IPackageInstallObserver} if the parser was unable to retrieve the
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was unable to retrieve the
* AndroidManifest.xml file.
*
* @hide
@@ -1172,8 +1154,8 @@ public abstract class PackageManager {
public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101;
/**
- * Installation parse return code: this is passed to the
- * {@link IPackageInstallObserver} if the parser encountered an unexpected
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered an unexpected
* exception.
*
* @hide
@@ -1182,9 +1164,9 @@ public abstract class PackageManager {
public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102;
/**
- * Installation parse return code: this is passed to the
- * {@link IPackageInstallObserver} if the parser did not find any
- * certificates in the .apk.
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser did not find any certificates in
+ * the .apk.
*
* @hide
*/
@@ -1192,9 +1174,9 @@ public abstract class PackageManager {
public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103;
/**
- * Installation parse return code: this is passed to the
- * {@link IPackageInstallObserver} if the parser found inconsistent
- * certificates on the files in the .apk.
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser found inconsistent certificates on
+ * the files in the .apk.
*
* @hide
*/
@@ -1202,8 +1184,8 @@ public abstract class PackageManager {
public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104;
/**
- * Installation parse return code: this is passed to the
- * {@link IPackageInstallObserver} if the parser encountered a
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered a
* CertificateEncodingException in one of the files in the .apk.
*
* @hide
@@ -1212,9 +1194,9 @@ public abstract class PackageManager {
public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105;
/**
- * Installation parse return code: this is passed to the
- * {@link IPackageInstallObserver} if the parser encountered a bad or
- * missing package name in the manifest.
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered a bad or missing
+ * package name in the manifest.
*
* @hide
*/
@@ -1222,9 +1204,9 @@ public abstract class PackageManager {
public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106;
/**
- * Installation parse return code: this is passed to the
- * {@link IPackageInstallObserver} if the parser encountered a bad shared
- * user id name in the manifest.
+ * Installation parse return code: tthis is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered a bad shared user id
+ * name in the manifest.
*
* @hide
*/
@@ -1232,8 +1214,8 @@ public abstract class PackageManager {
public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107;
/**
- * Installation parse return code: this is passed to the
- * {@link IPackageInstallObserver} if the parser encountered some structural
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser encountered some structural
* problem in the manifest.
*
* @hide
@@ -1242,9 +1224,9 @@ public abstract class PackageManager {
public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108;
/**
- * Installation parse return code: this is passed to the
- * {@link IPackageInstallObserver} if the parser did not find any actionable
- * tags (instrumentation or application) in the manifest.
+ * Installation parse return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser did not find any actionable tags
+ * (instrumentation or application) in the manifest.
*
* @hide
*/
@@ -1252,9 +1234,9 @@ public abstract class PackageManager {
public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109;
/**
- * Installation failed return code: this is passed to the
- * {@link IPackageInstallObserver} if the system failed to install the
- * package because of system issues.
+ * Installation failed return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+ * because of system issues.
*
* @hide
*/
@@ -1262,24 +1244,23 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
/**
- * Installation failed return code: this is passed to the
- * {@link IPackageInstallObserver} if the system failed to install the
- * package because the user is restricted from installing apps.
+ * Installation failed return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+ * because the user is restricted from installing apps.
*
* @hide
*/
public static final int INSTALL_FAILED_USER_RESTRICTED = -111;
/**
- * Installation failed return code: this is passed to the
- * {@link IPackageInstallObserver} if the system failed to install the
- * package because it is attempting to define a permission that is already
- * defined by some existing package.
+ * Installation failed return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+ * because it is attempting to define a permission that is already defined by some existing
+ * package.
* <p>
- * The package name of the app which has already defined the permission is
- * passed to a {@link PackageInstallObserver}, if any, as the
- * {@link #EXTRA_FAILURE_EXISTING_PACKAGE} string extra; and the name of the
- * permission being redefined is passed in the
+ * The package name of the app which has already defined the permission is passed to a
+ * {@link PackageInstallObserver}, if any, as the {@link #EXTRA_FAILURE_EXISTING_PACKAGE} string
+ * extra; and the name of the permission being redefined is passed in the
* {@link #EXTRA_FAILURE_EXISTING_PERMISSION} string extra.
*
* @hide
@@ -1287,10 +1268,9 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_DUPLICATE_PERMISSION = -112;
/**
- * Installation failed return code: this is passed to the
- * {@link IPackageInstallObserver} if the system failed to install the
- * package because its packaged native code did not match any of the ABIs
- * supported by the system.
+ * Installation failed return code: this is passed in the
+ * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the system failed to install the package
+ * because its packaged native code did not match any of the ABIs supported by the system.
*
* @hide
*/
@@ -1322,6 +1302,7 @@ public abstract class PackageManager {
DELETE_ALL_USERS,
DELETE_SYSTEM_APP,
DELETE_DONT_KILL_APP,
+ DELETE_CHATTY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeleteFlags {}
@@ -1363,6 +1344,14 @@ public abstract class PackageManager {
public static final int DELETE_DONT_KILL_APP = 0x00000008;
/**
+ * Flag parameter for {@link #deletePackage} to indicate that package deletion
+ * should be chatty.
+ *
+ * @hide
+ */
+ public static final int DELETE_CHATTY = 0x80000000;
+
+ /**
* Return code for when package deletion succeeds. This is passed to the
* {@link IPackageDeleteObserver} if the system succeeded in deleting the
* package.
@@ -4718,16 +4707,6 @@ public abstract class PackageManager {
@Deprecated
public abstract void installPackage(
Uri packageURI,
- IPackageInstallObserver observer,
- @InstallFlags int flags,
- String installerPackageName);
- /**
- * @deprecated replaced by {@link PackageInstaller}
- * @hide
- */
- @Deprecated
- public abstract void installPackage(
- Uri packageURI,
PackageInstallObserver observer,
@InstallFlags int flags,
String installerPackageName);
@@ -5743,25 +5722,6 @@ public abstract class PackageManager {
}
/** {@hide} */
- public static class LegacyPackageInstallObserver extends PackageInstallObserver {
- private final IPackageInstallObserver mLegacy;
-
- public LegacyPackageInstallObserver(IPackageInstallObserver legacy) {
- mLegacy = legacy;
- }
-
- @Override
- public void onPackageInstalled(String basePackageName, int returnCode, String msg,
- Bundle extras) {
- if (mLegacy == null) return;
- try {
- mLegacy.packageInstalled(basePackageName, returnCode);
- } catch (RemoteException ignored) {
- }
- }
- }
-
- /** {@hide} */
public static class LegacyPackageDeleteObserver extends PackageDeleteObserver {
private final IPackageDeleteObserver mLegacy;
diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java
index 14cf8557..713cd109 100644
--- a/android/content/pm/PackageManagerInternal.java
+++ b/android/content/pm/PackageManagerInternal.java
@@ -164,6 +164,14 @@ public abstract class PackageManagerInternal {
@PackageInfoFlags int flags, int filterCallingUid, int userId);
/**
+ * Do a straight uid lookup for the given package/application in the given user.
+ * @see PackageManager#getPackageUidAsUser(String, int, int)
+ * @return The app's uid, or < 0 if the package was not found in that user
+ */
+ public abstract int getPackageUid(String packageName,
+ @PackageInfoFlags int flags, int userId);
+
+ /**
* Retrieve all of the information we know about a particular package/application.
* @param filterCallingUid The results will be filtered in the context of this UID instead
* of the calling UID.
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
index 1c5cf15d..ebeaad78 100644
--- a/android/content/pm/PackageParser.java
+++ b/android/content/pm/PackageParser.java
@@ -42,6 +42,7 @@ import static android.os.Build.VERSION_CODES.O;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -107,6 +108,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
@@ -678,6 +681,8 @@ public class PackageParser {
pi.overlayTarget = p.mOverlayTarget;
pi.overlayPriority = p.mOverlayPriority;
pi.isStaticOverlay = p.mIsStaticOverlay;
+ pi.compileSdkVersion = p.mCompileSdkVersion;
+ pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename;
pi.firstInstallTime = firstInstallTime;
pi.lastUpdateTime = lastUpdateTime;
if ((flags&PackageManager.GET_GIDS) != 0) {
@@ -816,22 +821,33 @@ public class PackageParser {
}
}
- public static final int PARSE_IS_SYSTEM = 1 << 0;
- public static final int PARSE_CHATTY = 1 << 1;
- public static final int PARSE_MUST_BE_APK = 1 << 2;
- public static final int PARSE_IGNORE_PROCESSES = 1 << 3;
- public static final int PARSE_FORWARD_LOCK = 1 << 4;
- public static final int PARSE_EXTERNAL_STORAGE = 1 << 5;
- public static final int PARSE_IS_SYSTEM_DIR = 1 << 6;
- public static final int PARSE_IS_PRIVILEGED = 1 << 7;
- public static final int PARSE_COLLECT_CERTIFICATES = 1 << 8;
- public static final int PARSE_TRUSTED_OVERLAY = 1 << 9;
- public static final int PARSE_ENFORCE_CODE = 1 << 10;
- /** @deprecated remove when fixing b/34761192 */
+ public static final int PARSE_MUST_BE_APK = 1 << 0;
+ public static final int PARSE_IGNORE_PROCESSES = 1 << 1;
+ public static final int PARSE_FORWARD_LOCK = 1 << 2;
+ public static final int PARSE_EXTERNAL_STORAGE = 1 << 3;
+ public static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
+ public static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
+ public static final int PARSE_ENFORCE_CODE = 1 << 6;
+ public static final int PARSE_FORCE_SDK = 1 << 7;
+ /** @deprecated remove when fixing b/68860689 */
@Deprecated
- public static final int PARSE_IS_EPHEMERAL = 1 << 11;
- public static final int PARSE_FORCE_SDK = 1 << 12;
- public static final int PARSE_IS_OEM = 1 << 13;
+ public static final int PARSE_IS_EPHEMERAL = 1 << 8;
+ public static final int PARSE_CHATTY = 1 << 31;
+
+ @IntDef(flag = true, prefix = { "PARSE_" }, value = {
+ PARSE_CHATTY,
+ PARSE_COLLECT_CERTIFICATES,
+ PARSE_ENFORCE_CODE,
+ PARSE_EXTERNAL_STORAGE,
+ PARSE_FORCE_SDK,
+ PARSE_FORWARD_LOCK,
+ PARSE_IGNORE_PROCESSES,
+ PARSE_IS_EPHEMERAL,
+ PARSE_IS_SYSTEM_DIR,
+ PARSE_MUST_BE_APK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ParseFlags {}
private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
@@ -1242,9 +1258,12 @@ public class PackageParser {
}
}
- pkg.setCodePath(packageDir.getAbsolutePath());
+ pkg.setCodePath(packageDir.getCanonicalPath());
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
+ } catch (IOException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to get path: " + lite.baseCodePath, e);
} finally {
IoUtils.closeQuietly(assetLoader);
}
@@ -1273,9 +1292,12 @@ public class PackageParser {
try {
final Package pkg = parseBaseApk(apkFile, assets, flags);
- pkg.setCodePath(apkFile.getAbsolutePath());
+ pkg.setCodePath(apkFile.getCanonicalPath());
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
+ } catch (IOException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to get path: " + apkFile, e);
} finally {
IoUtils.closeQuietly(assets);
}
@@ -1505,7 +1527,7 @@ public class PackageParser {
* populating {@link Package#mSignatures}. Also asserts that all APK
* contents are signed correctly and consistently.
*/
- public static void collectCertificates(Package pkg, int parseFlags)
+ public static void collectCertificates(Package pkg, @ParseFlags int parseFlags)
throws PackageParserException {
collectCertificatesInternal(pkg, parseFlags);
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
@@ -1517,7 +1539,7 @@ public class PackageParser {
}
}
- private static void collectCertificatesInternal(Package pkg, int parseFlags)
+ private static void collectCertificatesInternal(Package pkg, @ParseFlags int parseFlags)
throws PackageParserException {
pkg.mCertificates = null;
pkg.mSignatures = null;
@@ -1537,7 +1559,7 @@ public class PackageParser {
}
}
- private static void collectCertificates(Package pkg, File apkFile, int parseFlags)
+ private static void collectCertificates(Package pkg, File apkFile, @ParseFlags int parseFlags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
@@ -2076,6 +2098,16 @@ public class PackageParser {
pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
+ pkg.mCompileSdkVersion = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0);
+ pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion;
+ pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0);
+ if (pkg.mCompileSdkVersionCodename != null) {
+ pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern();
+ }
+ pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename;
+
sa.recycle();
return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
@@ -2442,7 +2474,7 @@ public class PackageParser {
sa.recycle();
- if (name != null && (flags&PARSE_IS_SYSTEM) != 0) {
+ if (name != null) {
if (pkg.protectedBroadcasts == null) {
pkg.protectedBroadcasts = new ArrayList<String>();
}
@@ -3243,9 +3275,6 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags, 0);
perm.info.priority = sa.getInt(
com.android.internal.R.styleable.AndroidManifestPermissionGroup_priority, 0);
- if (perm.info.priority > 0 && (flags&PARSE_IS_SYSTEM) == 0) {
- perm.info.priority = 0;
- }
sa.recycle();
@@ -3551,17 +3580,14 @@ public class PackageParser {
ai.descriptionRes = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestApplication_description, 0);
- if ((flags&PARSE_IS_SYSTEM) != 0) {
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestApplication_persistent,
- false)) {
- // Check if persistence is based on a feature being present
- final String requiredFeature = sa.getNonResourceString(
- com.android.internal.R.styleable.
- AndroidManifestApplication_persistentWhenFeatureAvailable);
- if (requiredFeature == null || mCallback.hasFeature(requiredFeature)) {
- ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
- }
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_persistent,
+ false)) {
+ // Check if persistence is based on a feature being present
+ final String requiredFeature = sa.getNonResourceString(com.android.internal.R.styleable
+ .AndroidManifestApplication_persistentWhenFeatureAvailable);
+ if (requiredFeature == null || mCallback.hasFeature(requiredFeature)) {
+ ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
}
}
@@ -4431,13 +4457,6 @@ public class PackageParser {
if (sa.getBoolean(R.styleable.AndroidManifestActivity_singleUser, false)) {
a.info.flags |= ActivityInfo.FLAG_SINGLE_USER;
- if (a.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
- Slog.w(TAG, "Activity exported request ignored due to singleUser: "
- + a.className + " at " + mArchiveSourcePath + " "
- + parser.getPositionDescription());
- a.info.exported = false;
- setExported = true;
- }
}
a.info.encryptionAware = a.info.directBootAware = sa.getBoolean(
@@ -5026,12 +5045,6 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestProvider_singleUser,
false)) {
p.info.flags |= ProviderInfo.FLAG_SINGLE_USER;
- if (p.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
- Slog.w(TAG, "Provider exported request ignored due to singleUser: "
- + p.className + " at " + mArchiveSourcePath + " "
- + parser.getPositionDescription());
- p.info.exported = false;
- }
}
p.info.encryptionAware = p.info.directBootAware = sa.getBoolean(
@@ -5353,13 +5366,6 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestService_singleUser,
false)) {
s.info.flags |= ServiceInfo.FLAG_SINGLE_USER;
- if (s.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
- Slog.w(TAG, "Service exported request ignored due to singleUser: "
- + s.className + " at " + mArchiveSourcePath + " "
- + parser.getPositionDescription());
- s.info.exported = false;
- setExported = true;
- }
}
s.info.encryptionAware = s.info.directBootAware = sa.getBoolean(
@@ -5967,6 +5973,9 @@ public class PackageParser {
public boolean mIsStaticOverlay;
public boolean mTrustedOverlay;
+ public int mCompileSdkVersion;
+ public String mCompileSdkVersionCodename;
+
/**
* Data used to feed the KeySetManagerService
*/
@@ -6458,6 +6467,8 @@ public class PackageParser {
mOverlayPriority = dest.readInt();
mIsStaticOverlay = (dest.readInt() == 1);
mTrustedOverlay = (dest.readInt() == 1);
+ mCompileSdkVersion = dest.readInt();
+ mCompileSdkVersionCodename = dest.readString();
mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot);
mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot);
@@ -6581,6 +6592,8 @@ public class PackageParser {
dest.writeInt(mOverlayPriority);
dest.writeInt(mIsStaticOverlay ? 1 : 0);
dest.writeInt(mTrustedOverlay ? 1 : 0);
+ dest.writeInt(mCompileSdkVersion);
+ dest.writeString(mCompileSdkVersionCodename);
dest.writeArraySet(mSigningKeys);
dest.writeArraySet(mUpgradeKeySets);
writeKeySetMapping(dest, mKeySetMapping);
diff --git a/android/content/pm/PermissionInfo.java b/android/content/pm/PermissionInfo.java
index 5dd7aeda..75887624 100644
--- a/android/content/pm/PermissionInfo.java
+++ b/android/content/pm/PermissionInfo.java
@@ -155,12 +155,18 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
/**
* The level of access this permission is protecting, as per
- * {@link android.R.attr#protectionLevel}. Values may be
- * {@link #PROTECTION_NORMAL}, {@link #PROTECTION_DANGEROUS}, or
- * {@link #PROTECTION_SIGNATURE}. May also include the additional
- * flags {@link #PROTECTION_FLAG_SYSTEM} or {@link #PROTECTION_FLAG_DEVELOPMENT}
- * (which only make sense in combination with the base
- * {@link #PROTECTION_SIGNATURE}.
+ * {@link android.R.attr#protectionLevel}. Consists of
+ * a base permission type and zero or more flags:
+ *
+ * <pre>
+ * int basePermissionType = protectionLevel & {@link #PROTECTION_MASK_BASE};
+ * int permissionFlags = protectionLevel & {@link #PROTECTION_MASK_FLAGS};
+ * </pre>
+ *
+ * <p></p>Base permission types are {@link #PROTECTION_NORMAL},
+ * {@link #PROTECTION_DANGEROUS}, {@link #PROTECTION_SIGNATURE}
+ * and the deprecated {@link #PROTECTION_SIGNATURE_OR_SYSTEM}.
+ * Flags are listed under {@link android.R.attr#protectionLevel}.
*/
public int protectionLevel;
diff --git a/android/database/sqlite/SQLiteConnectionPool.java b/android/database/sqlite/SQLiteConnectionPool.java
index 8b0fef4f..5adb1196 100644
--- a/android/database/sqlite/SQLiteConnectionPool.java
+++ b/android/database/sqlite/SQLiteConnectionPool.java
@@ -570,6 +570,16 @@ public final class SQLiteConnectionPool implements Closeable {
mAvailableNonPrimaryConnections.clear();
}
+ /**
+ * Close non-primary connections that are not currently in use. This method is safe to use
+ * in finalize block as it doesn't throw RuntimeExceptions.
+ */
+ void closeAvailableNonPrimaryConnectionsAndLogExceptions() {
+ synchronized (mLock) {
+ closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
+ }
+ }
+
// Can't throw.
private void closeExcessConnectionsAndLogExceptionsLocked() {
int availableCount = mAvailableNonPrimaryConnections.size();
diff --git a/android/database/sqlite/SQLiteDatabase.java b/android/database/sqlite/SQLiteDatabase.java
index 863fb198..09bb9c69 100644
--- a/android/database/sqlite/SQLiteDatabase.java
+++ b/android/database/sqlite/SQLiteDatabase.java
@@ -1740,7 +1740,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
private int executeSql(String sql, Object[] bindArgs) throws SQLException {
acquireReference();
try {
- if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
+ final int statementType = DatabaseUtils.getSqlStatementType(sql);
+ if (statementType == DatabaseUtils.STATEMENT_ATTACH) {
boolean disableWal = false;
synchronized (mLock) {
if (!mHasAttachedDbsLocked) {
@@ -1754,11 +1755,14 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
}
- SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
- try {
+ try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) {
return statement.executeUpdateDelete();
} finally {
- statement.close();
+ // If schema was updated, close non-primary connections, otherwise they might
+ // have outdated schema information
+ if (statementType == DatabaseUtils.STATEMENT_DDL) {
+ mConnectionPoolLocked.closeAvailableNonPrimaryConnectionsAndLogExceptions();
+ }
}
} finally {
releaseReference();
diff --git a/android/graphics/Paint_Delegate.java b/android/graphics/Paint_Delegate.java
index 62427020..4f051761 100644
--- a/android/graphics/Paint_Delegate.java
+++ b/android/graphics/Paint_Delegate.java
@@ -125,8 +125,13 @@ public class Paint_Delegate {
*/
@NonNull
public List<FontInfo> getFonts() {
- if (mTypeface == null) {
- return Collections.emptyList();
+ Typeface_Delegate typeface = mTypeface;
+ if (typeface == null) {
+ if (Typeface.sDefaultTypeface == null) {
+ return Collections.emptyList();
+ }
+
+ typeface = Typeface_Delegate.getDelegate(Typeface.sDefaultTypeface.native_instance);
}
if (mFonts != null) {
@@ -138,7 +143,7 @@ public class Paint_Delegate {
new AffineTransform(mTextScaleX, mTextSkewX, 0, 1, 0, 0) :
null;
- List<FontInfo> infoList = StreamSupport.stream(mTypeface.getFonts(mFontVariant).spliterator
+ List<FontInfo> infoList = StreamSupport.stream(typeface.getFonts(mFontVariant).spliterator
(), false)
.map(font -> getFontInfo(font, mTextSize, affineTransform))
.collect(Collectors.toList());
diff --git a/android/hardware/camera2/CameraCharacteristics.java b/android/hardware/camera2/CameraCharacteristics.java
index 46ad3f0e..3a3048ef 100644
--- a/android/hardware/camera2/CameraCharacteristics.java
+++ b/android/hardware/camera2/CameraCharacteristics.java
@@ -1280,11 +1280,11 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <ul>
* <li>Processed (but stalling): any non-RAW format with a stallDurations &gt; 0.
* Typically {@link android.graphics.ImageFormat#JPEG JPEG format}.</li>
- * <li>Raw formats: {@link android.graphics.ImageFormat#RAW_SENSOR RAW_SENSOR}, {@link android.graphics.ImageFormat#RAW10 RAW10}, or {@link android.graphics.ImageFormat#RAW12 RAW12}.</li>
- * <li>Processed (but not-stalling): any non-RAW format without a stall duration.
- * Typically {@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888},
- * {@link android.graphics.ImageFormat#NV21 NV21}, or
- * {@link android.graphics.ImageFormat#YV12 YV12}.</li>
+ * <li>Raw formats: {@link android.graphics.ImageFormat#RAW_SENSOR RAW_SENSOR}, {@link android.graphics.ImageFormat#RAW10 RAW10}, or
+ * {@link android.graphics.ImageFormat#RAW12 RAW12}.</li>
+ * <li>Processed (but not-stalling): any non-RAW format without a stall duration. Typically
+ * {@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888},
+ * {@link android.graphics.ImageFormat#NV21 NV21}, or {@link android.graphics.ImageFormat#YV12 YV12}.</li>
* </ul>
* <p><b>Range of valid values:</b><br></p>
* <p>For processed (and stalling) format streams, &gt;= 1.</p>
@@ -1376,8 +1376,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* CPU resources that will consume more power. The image format for this kind of an output stream can
* be any non-<code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p>
* <p>A processed and stalling format is defined as any non-RAW format with a stallDurations
- * &gt; 0. Typically only the {@link android.graphics.ImageFormat#JPEG JPEG format} is a
- * stalling format.</p>
+ * &gt; 0. Typically only the {@link android.graphics.ImageFormat#JPEG JPEG format} is a stalling format.</p>
* <p>For full guarantees, query {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration } with a
* processed format -- it will return a non-0 value for a stalling stream.</p>
* <p>LEGACY devices will support up to 1 processing/stalling stream.</p>
@@ -1535,8 +1534,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<int[]>("android.request.availableRequestKeys", int[].class);
/**
- * <p>A list of all keys that the camera device has available
- * to use with {@link android.hardware.camera2.CaptureResult }.</p>
+ * <p>A list of all keys that the camera device has available to use with {@link android.hardware.camera2.CaptureResult }.</p>
* <p>Attempting to get a key from a CaptureResult that is not
* listed here will always return a <code>null</code> value. Getting a key from
* a CaptureResult that is listed here will generally never return a <code>null</code>
@@ -1561,8 +1559,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<int[]>("android.request.availableResultKeys", int[].class);
/**
- * <p>A list of all keys that the camera device has available
- * to use with {@link android.hardware.camera2.CameraCharacteristics }.</p>
+ * <p>A list of all keys that the camera device has available to use with {@link android.hardware.camera2.CameraCharacteristics }.</p>
* <p>This entry follows the same rules as
* android.request.availableResultKeys (except that it applies for
* CameraCharacteristics instead of CaptureResult). See above for more
@@ -1843,8 +1840,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
* android.scaler.availableStallDurations for more details about
* calculating the max frame rate.</p>
- * <p>(Keep in sync with
- * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration })</p>
* <p><b>Units</b>: (format, width, height, ns) x n</p>
* <p>This key is available on all devices.</p>
*
@@ -1905,14 +1900,13 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <ul>
* <li>{@link android.graphics.ImageFormat#YUV_420_888 }</li>
* <li>{@link android.graphics.ImageFormat#RAW10 }</li>
+ * <li>{@link android.graphics.ImageFormat#RAW12 }</li>
* </ul>
* <p>All other formats may or may not have an allowed stall duration on
* a per-capability basis; refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
* for more details.</p>
* <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} for more information about
* calculating the max frame rate (absent stalls).</p>
- * <p>(Keep up to date with
- * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration } )</p>
* <p><b>Units</b>: (format, width, height, ns) x n</p>
* <p>This key is available on all devices.</p>
*
@@ -2195,9 +2189,9 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* the raw buffers produced by this sensor.</p>
* <p>If a camera device supports raw sensor formats, either this or
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} is the maximum dimensions for the raw
- * output formats listed in {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} (this depends on
- * whether or not the image sensor returns buffers containing pixels that are not
- * part of the active array region for blacklevel calibration or other purposes).</p>
+ * output formats listed in {@link android.hardware.camera2.params.StreamConfigurationMap }
+ * (this depends on whether or not the image sensor returns buffers containing pixels that
+ * are not part of the active array region for blacklevel calibration or other purposes).</p>
* <p>Some parts of the full pixel array may not receive light from the scene,
* or be otherwise inactive. The {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} key
* defines the rectangle of active pixels that will be included in processed image
@@ -2205,7 +2199,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p><b>Units</b>: Pixels</p>
* <p>This key is available on all devices.</p>
*
- * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
* @see CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
*/
@@ -2838,7 +2831,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>See the individual level enums for full descriptions of the supported capabilities. The
* {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} entry describes the device's capabilities at a
* finer-grain level, if needed. In addition, many controls have their available settings or
- * ranges defined in individual {@link android.hardware.camera2.CameraCharacteristics } entries.</p>
+ * ranges defined in individual entries from {@link android.hardware.camera2.CameraCharacteristics }.</p>
* <p>Some features are not part of any particular hardware level or capability and must be
* queried separately. These include:</p>
* <ul>
@@ -2973,7 +2966,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
* android.scaler.availableStallDurations for more details about
* calculating the max frame rate.</p>
- * <p>(Keep in sync with {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration })</p>
* <p><b>Units</b>: (format, width, height, ns) x n</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Limited capability</b> -
diff --git a/android/hardware/camera2/CameraMetadata.java b/android/hardware/camera2/CameraMetadata.java
index 8c8c49fa..4b57018b 100644
--- a/android/hardware/camera2/CameraMetadata.java
+++ b/android/hardware/camera2/CameraMetadata.java
@@ -587,8 +587,8 @@ public abstract class CameraMetadata<TKey> {
* then the list of resolutions for YUV_420_888 from {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes } contains at
* least one resolution &gt;= 8 megapixels, with a minimum frame duration of &lt;= 1/20
* s.</p>
- * <p>If the device supports the {@link android.graphics.ImageFormat#RAW10 }, {@link android.graphics.ImageFormat#RAW12 }, then those can also be captured at the same rate
- * as the maximum-size YUV_420_888 resolution is.</p>
+ * <p>If the device supports the {@link android.graphics.ImageFormat#RAW10 }, {@link android.graphics.ImageFormat#RAW12 }, then those can also be
+ * captured at the same rate as the maximum-size YUV_420_888 resolution is.</p>
* <p>If the device supports the PRIVATE_REPROCESSING capability, then the same guarantees
* as for the YUV_420_888 format also apply to the {@link android.graphics.ImageFormat#PRIVATE } format.</p>
* <p>In addition, the {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} field is guaranted to have a value between 0
@@ -610,25 +610,22 @@ public abstract class CameraMetadata<TKey> {
* following:</p>
* <ul>
* <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li>
- * <li>{@link android.graphics.ImageFormat#YUV_420_888 } is supported as an output/input format, that is,
- * YUV_420_888 is included in the lists of formats returned by
- * {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and
- * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.</li>
+ * <li>{@link android.graphics.ImageFormat#YUV_420_888 } is supported as an output/input
+ * format, that is, YUV_420_888 is included in the lists of formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.</li>
* <li>{@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput }
* returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.</li>
* <li>Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(YUV_420_888)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(YUV_420_888)}</li>
- * <li>Using {@link android.graphics.ImageFormat#YUV_420_888 } does not cause a frame rate drop
- * relative to the sensor's maximum capture rate (at that resolution).</li>
+ * <li>Using {@link android.graphics.ImageFormat#YUV_420_888 } does not cause a frame rate
+ * drop relative to the sensor's maximum capture rate (at that resolution).</li>
* <li>{@link android.graphics.ImageFormat#YUV_420_888 } will be reprocessable into both
* {@link android.graphics.ImageFormat#YUV_420_888 } and {@link android.graphics.ImageFormat#JPEG } formats.</li>
* <li>The maximum available resolution for {@link android.graphics.ImageFormat#YUV_420_888 } streams (both input/output) will match the
* maximum available resolution of {@link android.graphics.ImageFormat#JPEG } streams.</li>
* <li>Static metadata {@link CameraCharacteristics#REPROCESS_MAX_CAPTURE_STALL android.reprocess.maxCaptureStall}.</li>
* <li>Only the below controls are effective for reprocessing requests and will be present
- * in capture results. The reprocess requests are from the original capture results that
- * are associated with the intermediate {@link android.graphics.ImageFormat#YUV_420_888 }
- * output buffers. All other controls in the reprocess requests will be ignored by the
- * camera device.<ul>
+ * in capture results. The reprocess requests are from the original capture results
+ * that are associated with the intermediate {@link android.graphics.ImageFormat#YUV_420_888 } output buffers. All other controls in the
+ * reprocess requests will be ignored by the camera device.<ul>
* <li>android.jpeg.*</li>
* <li>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}</li>
* <li>{@link CaptureRequest#EDGE_MODE android.edge.mode}</li>
@@ -654,13 +651,13 @@ public abstract class CameraMetadata<TKey> {
* <p>The camera device can produce depth measurements from its field of view.</p>
* <p>This capability requires the camera device to support the following:</p>
* <ul>
- * <li>{@link android.graphics.ImageFormat#DEPTH16 } is supported as an output format.</li>
- * <li>{@link android.graphics.ImageFormat#DEPTH_POINT_CLOUD } is optionally supported as an
- * output format.</li>
- * <li>This camera device, and all camera devices with the same {@link CameraCharacteristics#LENS_FACING android.lens.facing},
- * will list the following calibration entries in both
- * {@link android.hardware.camera2.CameraCharacteristics } and
- * {@link android.hardware.camera2.CaptureResult }:<ul>
+ * <li>{@link android.graphics.ImageFormat#DEPTH16 } is supported as
+ * an output format.</li>
+ * <li>{@link android.graphics.ImageFormat#DEPTH_POINT_CLOUD } is
+ * optionally supported as an output format.</li>
+ * <li>This camera device, and all camera devices with the same {@link CameraCharacteristics#LENS_FACING android.lens.facing}, will
+ * list the following calibration metadata entries in both {@link android.hardware.camera2.CameraCharacteristics }
+ * and {@link android.hardware.camera2.CaptureResult }:<ul>
* <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li>
* <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li>
* <li>{@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}</li>
@@ -674,8 +671,7 @@ public abstract class CameraMetadata<TKey> {
* </ul>
* <p>Generally, depth output operates at a slower frame rate than standard color capture,
* so the DEPTH16 and DEPTH_POINT_CLOUD formats will commonly have a stall duration that
- * should be accounted for (see
- * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }).
+ * should be accounted for (see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }).
* On a device that supports both depth and color-based output, to enable smooth preview,
* using a repeating burst is recommended, where a depth-output target is only included
* once every N frames, where N is the ratio between preview output rate and depth output
@@ -692,23 +688,19 @@ public abstract class CameraMetadata<TKey> {
public static final int REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8;
/**
- * <p>The device supports constrained high speed video recording (frame rate &gt;=120fps)
- * use case. The camera device will support high speed capture session created by
- * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }, which
- * only accepts high speed request lists created by
- * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList }.</p>
- * <p>A camera device can still support high speed video streaming by advertising the high speed
- * FPS ranges in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}. For this case, all normal
- * capture request per frame control and synchronization requirements will apply to
- * the high speed fps ranges, the same as all other fps ranges. This capability describes
- * the capability of a specialized operating mode with many limitations (see below), which
- * is only targeted at high speed video recording.</p>
- * <p>The supported high speed video sizes and fps ranges are specified in
- * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }.
- * To get desired output frame rates, the application is only allowed to select video size
- * and FPS range combinations provided by
- * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes }.
- * The fps range can be controlled via {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}.</p>
+ * <p>The device supports constrained high speed video recording (frame rate &gt;=120fps) use
+ * case. The camera device will support high speed capture session created by {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }, which
+ * only accepts high speed request lists created by {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList }.</p>
+ * <p>A camera device can still support high speed video streaming by advertising the high
+ * speed FPS ranges in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}. For this case, all
+ * normal capture request per frame control and synchronization requirements will apply
+ * to the high speed fps ranges, the same as all other fps ranges. This capability
+ * describes the capability of a specialized operating mode with many limitations (see
+ * below), which is only targeted at high speed video recording.</p>
+ * <p>The supported high speed video sizes and fps ranges are specified in {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }.
+ * To get desired output frame rates, the application is only allowed to select video
+ * size and FPS range combinations provided by {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes }. The
+ * fps range can be controlled via {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}.</p>
* <p>In this capability, the camera device will override aeMode, awbMode, and afMode to
* ON, AUTO, and CONTINUOUS_VIDEO, respectively. All post-processing block mode
* controls will be overridden to be FAST. Therefore, no manual control of capture
@@ -743,19 +735,16 @@ public abstract class CameraMetadata<TKey> {
* frame rate. If the destination surface is from preview window, the actual preview frame
* rate will be bounded by the screen refresh rate.</p>
* <p>The camera device will only support up to 2 high speed simultaneous output surfaces
- * (preview and recording surfaces)
- * in this mode. Above controls will be effective only if all of below conditions are true:</p>
+ * (preview and recording surfaces) in this mode. Above controls will be effective only
+ * if all of below conditions are true:</p>
* <ul>
* <li>The application creates a camera capture session with no more than 2 surfaces via
* {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }. The
- * targeted surfaces must be preview surface (either from
- * {@link android.view.SurfaceView } or {@link android.graphics.SurfaceTexture }) or
- * recording surface(either from {@link android.media.MediaRecorder#getSurface } or
- * {@link android.media.MediaCodec#createInputSurface }).</li>
+ * targeted surfaces must be preview surface (either from {@link android.view.SurfaceView } or {@link android.graphics.SurfaceTexture }) or recording
+ * surface(either from {@link android.media.MediaRecorder#getSurface } or {@link android.media.MediaCodec#createInputSurface }).</li>
* <li>The stream sizes are selected from the sizes reported by
* {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes }.</li>
- * <li>The FPS ranges are selected from
- * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }.</li>
+ * <li>The FPS ranges are selected from {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }.</li>
* </ul>
* <p>When above conditions are NOT satistied,
* {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }
@@ -1038,8 +1027,7 @@ public abstract class CameraMetadata<TKey> {
/**
* <p>This camera device is running in backward compatibility mode.</p>
- * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the {@link android.hardware.camera2.CameraDevice#createCaptureSession createCaptureSession}
- * documentation are supported.</p>
+ * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the {@link android.hardware.camera2.CameraDevice#createCaptureSession createCaptureSession} documentation are supported.</p>
* <p>A <code>LEGACY</code> device does not support per-frame control, manual sensor control, manual
* post-processing, arbitrary cropping regions, and has relaxed performance constraints.
* No additional capabilities beyond <code>BACKWARD_COMPATIBLE</code> will ever be listed by a
@@ -1061,8 +1049,7 @@ public abstract class CameraMetadata<TKey> {
* <p>This camera device is capable of YUV reprocessing and RAW data capture, in addition to
* FULL-level capabilities.</p>
* <p>The stream configurations listed in the <code>LEVEL_3</code>, <code>RAW</code>, <code>FULL</code>, <code>LEGACY</code> and
- * <code>LIMITED</code> tables in the {@link android.hardware.camera2.CameraDevice#createCaptureSession createCaptureSession}
- * documentation are guaranteed to be supported.</p>
+ * <code>LIMITED</code> tables in the {@link android.hardware.camera2.CameraDevice#createCaptureSession createCaptureSession} documentation are guaranteed to be supported.</p>
* <p>The following additional capabilities are guaranteed to be supported:</p>
* <ul>
* <li><code>YUV_REPROCESSING</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
@@ -2155,12 +2142,13 @@ public abstract class CameraMetadata<TKey> {
public static final int EDGE_MODE_HIGH_QUALITY = 2;
/**
- * <p>Edge enhancement is applied at different levels for different output streams,
- * based on resolution. Streams at maximum recording resolution (see {@link android.hardware.camera2.CameraDevice#createCaptureSession }) or below have
- * edge enhancement applied, while higher-resolution streams have no edge enhancement
- * applied. The level of edge enhancement for low-resolution streams is tuned so that
- * frame rate is not impacted, and the quality is equal to or better than FAST (since it
- * is only applied to lower-resolution outputs, quality may improve from FAST).</p>
+ * <p>Edge enhancement is applied at different
+ * levels for different output streams, based on resolution. Streams at maximum recording
+ * resolution (see {@link android.hardware.camera2.CameraDevice#createCaptureSession })
+ * or below have edge enhancement applied, while higher-resolution streams have no edge
+ * enhancement applied. The level of edge enhancement for low-resolution streams is tuned
+ * so that frame rate is not impacted, and the quality is equal to or better than FAST
+ * (since it is only applied to lower-resolution outputs, quality may improve from FAST).</p>
* <p>This mode is intended to be used by applications operating in a zero-shutter-lag mode
* with YUV or PRIVATE reprocessing, where the application continuously captures
* high-resolution intermediate buffers into a circular buffer, from which a final image is
@@ -2287,12 +2275,12 @@ public abstract class CameraMetadata<TKey> {
/**
* <p>Noise reduction is applied at different levels for different output streams,
- * based on resolution. Streams at maximum recording resolution (see {@link android.hardware.camera2.CameraDevice#createCaptureSession }) or below have noise
- * reduction applied, while higher-resolution streams have MINIMAL (if supported) or no
- * noise reduction applied (if MINIMAL is not supported.) The degree of noise reduction
- * for low-resolution streams is tuned so that frame rate is not impacted, and the quality
- * is equal to or better than FAST (since it is only applied to lower-resolution outputs,
- * quality may improve from FAST).</p>
+ * based on resolution. Streams at maximum recording resolution (see {@link android.hardware.camera2.CameraDevice#createCaptureSession })
+ * or below have noise reduction applied, while higher-resolution streams have MINIMAL (if
+ * supported) or no noise reduction applied (if MINIMAL is not supported.) The degree of
+ * noise reduction for low-resolution streams is tuned so that frame rate is not impacted,
+ * and the quality is equal to or better than FAST (since it is only applied to
+ * lower-resolution outputs, quality may improve from FAST).</p>
* <p>This mode is intended to be used by applications operating in a zero-shutter-lag mode
* with YUV or PRIVATE reprocessing, where the application continuously captures
* high-resolution intermediate buffers into a circular buffer, from which a final image is
diff --git a/android/hardware/camera2/CaptureRequest.java b/android/hardware/camera2/CaptureRequest.java
index c41fc020..0262ecb5 100644
--- a/android/hardware/camera2/CaptureRequest.java
+++ b/android/hardware/camera2/CaptureRequest.java
@@ -680,7 +680,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* FAST or HIGH_QUALITY will yield a picture with the same white point
* as what was produced by the camera device in the earlier frame.</p>
* <p>The expected processing pipeline is as follows:</p>
- * <p><img alt="White balance processing pipeline" src="../../../../images/camera2/metadata/android.colorCorrection.mode/processing_pipeline.png" /></p>
+ * <p><img alt="White balance processing pipeline" src="/reference/images/camera2/metadata/android.colorCorrection.mode/processing_pipeline.png" /></p>
* <p>The white balance is encoded by two values, a 4-channel white-balance
* gain vector (applied in the Bayer domain), and a 3x3 color transform
* matrix (applied after demosaic).</p>
@@ -1470,10 +1470,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>When set to AUTO, the individual algorithm controls in
* android.control.* are in effect, such as {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}.</p>
* <p>When set to USE_SCENE_MODE, the individual controls in
- * android.control.* are mostly disabled, and the camera device implements
- * one of the scene mode settings (such as ACTION, SUNSET, or PARTY)
- * as it wishes. The camera device scene mode 3A settings are provided by
- * {@link android.hardware.camera2.CaptureResult capture results}.</p>
+ * android.control.* are mostly disabled, and the camera device
+ * implements one of the scene mode settings (such as ACTION,
+ * SUNSET, or PARTY) as it wishes. The camera device scene mode
+ * 3A settings are provided by {@link android.hardware.camera2.CaptureResult capture results}.</p>
* <p>When set to OFF_KEEP_STATE, it is similar to OFF mode, the only difference
* is that this frame will not be used by camera device background 3A statistics
* update, as if this frame is never captured. This mode can be used in the scenario
@@ -2268,45 +2268,35 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* can run concurrently to the rest of the camera pipeline, but
* cannot process more than 1 capture at a time.</li>
* </ul>
- * <p>The necessary information for the application, given the model above,
- * is provided via the {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} field using
+ * <p>The necessary information for the application, given the model above, is provided via
* {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration }.
- * These are used to determine the maximum frame rate / minimum frame
- * duration that is possible for a given stream configuration.</p>
+ * These are used to determine the maximum frame rate / minimum frame duration that is
+ * possible for a given stream configuration.</p>
* <p>Specifically, the application can use the following rules to
* determine the minimum frame duration it can request from the camera
* device:</p>
* <ol>
- * <li>Let the set of currently configured input/output streams
- * be called <code>S</code>.</li>
- * <li>Find the minimum frame durations for each stream in <code>S</code>, by looking
- * it up in {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} using {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration }
- * (with its respective size/format). Let this set of frame durations be
- * called <code>F</code>.</li>
- * <li>For any given request <code>R</code>, the minimum frame duration allowed
- * for <code>R</code> is the maximum out of all values in <code>F</code>. Let the streams
- * used in <code>R</code> be called <code>S_r</code>.</li>
+ * <li>Let the set of currently configured input/output streams be called <code>S</code>.</li>
+ * <li>Find the minimum frame durations for each stream in <code>S</code>, by looking it up in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration }
+ * (with its respective size/format). Let this set of frame durations be called <code>F</code>.</li>
+ * <li>For any given request <code>R</code>, the minimum frame duration allowed for <code>R</code> is the maximum
+ * out of all values in <code>F</code>. Let the streams used in <code>R</code> be called <code>S_r</code>.</li>
* </ol>
* <p>If none of the streams in <code>S_r</code> have a stall time (listed in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }
- * using its respective size/format), then the frame duration in <code>F</code>
- * determines the steady state frame rate that the application will get
- * if it uses <code>R</code> as a repeating request. Let this special kind of
- * request be called <code>Rsimple</code>.</p>
- * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved
- * by a single capture of a new request <code>Rstall</code> (which has at least
- * one in-use stream with a non-0 stall time) and if <code>Rstall</code> has the
- * same minimum frame duration this will not cause a frame rate loss
- * if all buffers from the previous <code>Rstall</code> have already been
- * delivered.</p>
- * <p>For more details about stalling, see
- * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p>
+ * using its respective size/format), then the frame duration in <code>F</code> determines the steady
+ * state frame rate that the application will get if it uses <code>R</code> as a repeating request. Let
+ * this special kind of request be called <code>Rsimple</code>.</p>
+ * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved by a single capture of a
+ * new request <code>Rstall</code> (which has at least one in-use stream with a non-0 stall time) and if
+ * <code>Rstall</code> has the same minimum frame duration this will not cause a frame rate loss if all
+ * buffers from the previous <code>Rstall</code> have already been delivered.</p>
+ * <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p>
* <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
* OFF; otherwise the auto-exposure algorithm will override this value.</p>
* <p><b>Units</b>: Nanoseconds</p>
* <p><b>Range of valid values:</b><br>
- * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration},
- * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}. The duration
- * is capped to <code>max(duration, exposureTime + overhead)</code>.</p>
+ * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }.
+ * The duration is capped to <code>max(duration, exposureTime + overhead)</code>.</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
@@ -2315,7 +2305,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_MODE
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
- * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
* @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION
*/
@PublicKey
@@ -2584,11 +2573,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>Linear mapping:</p>
* <pre><code>android.tonemap.curveRed = [ 0, 0, 1.0, 1.0 ]
* </code></pre>
- * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
+ * <p><img alt="Linear mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
* <p>Invert mapping:</p>
* <pre><code>android.tonemap.curveRed = [ 0, 1.0, 1.0, 0 ]
* </code></pre>
- * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
+ * <p><img alt="Inverting mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
* <p>Gamma 1/2.2 mapping, with 16 control points:</p>
* <pre><code>android.tonemap.curveRed = [
* 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812,
@@ -2596,7 +2585,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685,
* 0.8000, 0.9035, 0.8667, 0.9370, 0.9333, 0.9691, 1.0000, 1.0000 ]
* </code></pre>
- * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
+ * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
* <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p>
* <pre><code>android.tonemap.curveRed = [
* 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845,
@@ -2604,7 +2593,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721,
* 0.8000, 0.9063, 0.8667, 0.9389, 0.9333, 0.9701, 1.0000, 1.0000 ]
* </code></pre>
- * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ * <p><img alt="sRGB tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
* <p><b>Range of valid values:</b><br>
* 0-1 on both input and output coordinates, normalized
* as a floating-point value such that 0 == black and 1 == white.</p>
@@ -2646,11 +2635,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>Linear mapping:</p>
* <pre><code>curveRed = [ (0, 0), (1.0, 1.0) ]
* </code></pre>
- * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
+ * <p><img alt="Linear mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
* <p>Invert mapping:</p>
* <pre><code>curveRed = [ (0, 1.0), (1.0, 0) ]
* </code></pre>
- * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
+ * <p><img alt="Inverting mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
* <p>Gamma 1/2.2 mapping, with 16 control points:</p>
* <pre><code>curveRed = [
* (0.0000, 0.0000), (0.0667, 0.2920), (0.1333, 0.4002), (0.2000, 0.4812),
@@ -2658,7 +2647,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* (0.5333, 0.7515), (0.6000, 0.7928), (0.6667, 0.8317), (0.7333, 0.8685),
* (0.8000, 0.9035), (0.8667, 0.9370), (0.9333, 0.9691), (1.0000, 1.0000) ]
* </code></pre>
- * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
+ * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
* <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p>
* <pre><code>curveRed = [
* (0.0000, 0.0000), (0.0667, 0.2864), (0.1333, 0.4007), (0.2000, 0.4845),
@@ -2666,7 +2655,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* (0.5333, 0.7569), (0.6000, 0.7977), (0.6667, 0.8360), (0.7333, 0.8721),
* (0.8000, 0.9063), (0.8667, 0.9389), (0.9333, 0.9701), (1.0000, 1.0000) ]
* </code></pre>
- * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ * <p><img alt="sRGB tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
@@ -2756,9 +2745,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* PRESET_CURVE</p>
* <p>The tonemap curve will be defined by specified standard.</p>
* <p>sRGB (approximated by 16 control points):</p>
- * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ * <p><img alt="sRGB tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
* <p>Rec. 709 (approximated by 16 control points):</p>
- * <p><img alt="Rec. 709 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/rec709_tonemap.png" /></p>
+ * <p><img alt="Rec. 709 tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/rec709_tonemap.png" /></p>
* <p>Note that above figures show a 16 control points approximation of preset
* curves. Camera devices may apply a different approximation to the curve.</p>
* <p><b>Possible values:</b>
diff --git a/android/hardware/camera2/CaptureResult.java b/android/hardware/camera2/CaptureResult.java
index 6d80c20a..cfad098c 100644
--- a/android/hardware/camera2/CaptureResult.java
+++ b/android/hardware/camera2/CaptureResult.java
@@ -390,7 +390,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* FAST or HIGH_QUALITY will yield a picture with the same white point
* as what was produced by the camera device in the earlier frame.</p>
* <p>The expected processing pipeline is as follows:</p>
- * <p><img alt="White balance processing pipeline" src="../../../../images/camera2/metadata/android.colorCorrection.mode/processing_pipeline.png" /></p>
+ * <p><img alt="White balance processing pipeline" src="/reference/images/camera2/metadata/android.colorCorrection.mode/processing_pipeline.png" /></p>
* <p>The white balance is encoded by two values, a 4-channel white-balance
* gain vector (applied in the Bayer domain), and a 3x3 color transform
* matrix (applied after demosaic).</p>
@@ -1975,10 +1975,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>When set to AUTO, the individual algorithm controls in
* android.control.* are in effect, such as {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}.</p>
* <p>When set to USE_SCENE_MODE, the individual controls in
- * android.control.* are mostly disabled, and the camera device implements
- * one of the scene mode settings (such as ACTION, SUNSET, or PARTY)
- * as it wishes. The camera device scene mode 3A settings are provided by
- * {@link android.hardware.camera2.CaptureResult capture results}.</p>
+ * android.control.* are mostly disabled, and the camera device
+ * implements one of the scene mode settings (such as ACTION,
+ * SUNSET, or PARTY) as it wishes. The camera device scene mode
+ * 3A settings are provided by {@link android.hardware.camera2.CaptureResult capture results}.</p>
* <p>When set to OFF_KEEP_STATE, it is similar to OFF mode, the only difference
* is that this frame will not be used by camera device background 3A statistics
* update, as if this frame is never captured. This mode can be used in the scenario
@@ -3108,45 +3108,35 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* can run concurrently to the rest of the camera pipeline, but
* cannot process more than 1 capture at a time.</li>
* </ul>
- * <p>The necessary information for the application, given the model above,
- * is provided via the {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} field using
+ * <p>The necessary information for the application, given the model above, is provided via
* {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration }.
- * These are used to determine the maximum frame rate / minimum frame
- * duration that is possible for a given stream configuration.</p>
+ * These are used to determine the maximum frame rate / minimum frame duration that is
+ * possible for a given stream configuration.</p>
* <p>Specifically, the application can use the following rules to
* determine the minimum frame duration it can request from the camera
* device:</p>
* <ol>
- * <li>Let the set of currently configured input/output streams
- * be called <code>S</code>.</li>
- * <li>Find the minimum frame durations for each stream in <code>S</code>, by looking
- * it up in {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} using {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration }
- * (with its respective size/format). Let this set of frame durations be
- * called <code>F</code>.</li>
- * <li>For any given request <code>R</code>, the minimum frame duration allowed
- * for <code>R</code> is the maximum out of all values in <code>F</code>. Let the streams
- * used in <code>R</code> be called <code>S_r</code>.</li>
+ * <li>Let the set of currently configured input/output streams be called <code>S</code>.</li>
+ * <li>Find the minimum frame durations for each stream in <code>S</code>, by looking it up in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration }
+ * (with its respective size/format). Let this set of frame durations be called <code>F</code>.</li>
+ * <li>For any given request <code>R</code>, the minimum frame duration allowed for <code>R</code> is the maximum
+ * out of all values in <code>F</code>. Let the streams used in <code>R</code> be called <code>S_r</code>.</li>
* </ol>
* <p>If none of the streams in <code>S_r</code> have a stall time (listed in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }
- * using its respective size/format), then the frame duration in <code>F</code>
- * determines the steady state frame rate that the application will get
- * if it uses <code>R</code> as a repeating request. Let this special kind of
- * request be called <code>Rsimple</code>.</p>
- * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved
- * by a single capture of a new request <code>Rstall</code> (which has at least
- * one in-use stream with a non-0 stall time) and if <code>Rstall</code> has the
- * same minimum frame duration this will not cause a frame rate loss
- * if all buffers from the previous <code>Rstall</code> have already been
- * delivered.</p>
- * <p>For more details about stalling, see
- * {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p>
+ * using its respective size/format), then the frame duration in <code>F</code> determines the steady
+ * state frame rate that the application will get if it uses <code>R</code> as a repeating request. Let
+ * this special kind of request be called <code>Rsimple</code>.</p>
+ * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved by a single capture of a
+ * new request <code>Rstall</code> (which has at least one in-use stream with a non-0 stall time) and if
+ * <code>Rstall</code> has the same minimum frame duration this will not cause a frame rate loss if all
+ * buffers from the previous <code>Rstall</code> have already been delivered.</p>
+ * <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p>
* <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
* OFF; otherwise the auto-exposure algorithm will override this value.</p>
* <p><b>Units</b>: Nanoseconds</p>
* <p><b>Range of valid values:</b><br>
- * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration},
- * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}. The duration
- * is capped to <code>max(duration, exposureTime + overhead)</code>.</p>
+ * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }.
+ * The duration is capped to <code>max(duration, exposureTime + overhead)</code>.</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
@@ -3155,7 +3145,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_MODE
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
- * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
* @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION
*/
@PublicKey
@@ -3408,9 +3397,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* layout key (see {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}), i.e. the
* nth value given corresponds to the black level offset for the nth
* color channel listed in the CFA.</p>
- * <p>This key will be available if {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions} is
- * available or the camera device advertises this key via
- * {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureResultKeys }.</p>
+ * <p>This key will be available if {@link CameraCharacteristics#SENSOR_OPTICAL_BLACK_REGIONS android.sensor.opticalBlackRegions} is available or the
+ * camera device advertises this key via {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureResultKeys }.</p>
* <p><b>Range of valid values:</b><br>
* &gt;= 0 for each.</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
@@ -3640,13 +3628,13 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* </code></pre>
* <p>The low-resolution scaling map images for each channel are
* (displayed using nearest-neighbor interpolation):</p>
- * <p><img alt="Red lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" />
- * <img alt="Green (even rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" />
- * <img alt="Green (odd rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" />
- * <img alt="Blue lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p>
+ * <p><img alt="Red lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" />
+ * <img alt="Green (even rows) lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" />
+ * <img alt="Green (odd rows) lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" />
+ * <img alt="Blue lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p>
* <p>As a visualization only, inverting the full-color map to recover an
* image of a gray wall (using bicubic interpolation for visual quality) as captured by the sensor gives:</p>
- * <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p>
+ * <p><img alt="Image of a uniform white wall (inverse shading map)" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p>
* <p><b>Range of valid values:</b><br>
* Each gain factor is &gt;= 1</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
@@ -3707,14 +3695,14 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* </code></pre>
* <p>The low-resolution scaling map images for each channel are
* (displayed using nearest-neighbor interpolation):</p>
- * <p><img alt="Red lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" />
- * <img alt="Green (even rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" />
- * <img alt="Green (odd rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" />
- * <img alt="Blue lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p>
+ * <p><img alt="Red lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" />
+ * <img alt="Green (even rows) lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" />
+ * <img alt="Green (odd rows) lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" />
+ * <img alt="Blue lens shading map" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p>
* <p>As a visualization only, inverting the full-color map to recover an
* image of a gray wall (using bicubic interpolation for visual quality)
* as captured by the sensor gives:</p>
- * <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p>
+ * <p><img alt="Image of a uniform white wall (inverse shading map)" src="/reference/images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p>
* <p>Note that the RAW image data might be subject to lens shading
* correction not reported on this map. Query
* {@link CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED android.sensor.info.lensShadingApplied} to see if RAW image data has subject
@@ -3947,11 +3935,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>Linear mapping:</p>
* <pre><code>android.tonemap.curveRed = [ 0, 0, 1.0, 1.0 ]
* </code></pre>
- * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
+ * <p><img alt="Linear mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
* <p>Invert mapping:</p>
* <pre><code>android.tonemap.curveRed = [ 0, 1.0, 1.0, 0 ]
* </code></pre>
- * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
+ * <p><img alt="Inverting mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
* <p>Gamma 1/2.2 mapping, with 16 control points:</p>
* <pre><code>android.tonemap.curveRed = [
* 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812,
@@ -3959,7 +3947,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685,
* 0.8000, 0.9035, 0.8667, 0.9370, 0.9333, 0.9691, 1.0000, 1.0000 ]
* </code></pre>
- * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
+ * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
* <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p>
* <pre><code>android.tonemap.curveRed = [
* 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845,
@@ -3967,7 +3955,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721,
* 0.8000, 0.9063, 0.8667, 0.9389, 0.9333, 0.9701, 1.0000, 1.0000 ]
* </code></pre>
- * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ * <p><img alt="sRGB tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
* <p><b>Range of valid values:</b><br>
* 0-1 on both input and output coordinates, normalized
* as a floating-point value such that 0 == black and 1 == white.</p>
@@ -4009,11 +3997,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>Linear mapping:</p>
* <pre><code>curveRed = [ (0, 0), (1.0, 1.0) ]
* </code></pre>
- * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
+ * <p><img alt="Linear mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
* <p>Invert mapping:</p>
* <pre><code>curveRed = [ (0, 1.0), (1.0, 0) ]
* </code></pre>
- * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
+ * <p><img alt="Inverting mapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
* <p>Gamma 1/2.2 mapping, with 16 control points:</p>
* <pre><code>curveRed = [
* (0.0000, 0.0000), (0.0667, 0.2920), (0.1333, 0.4002), (0.2000, 0.4812),
@@ -4021,7 +4009,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* (0.5333, 0.7515), (0.6000, 0.7928), (0.6667, 0.8317), (0.7333, 0.8685),
* (0.8000, 0.9035), (0.8667, 0.9370), (0.9333, 0.9691), (1.0000, 1.0000) ]
* </code></pre>
- * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
+ * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
* <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p>
* <pre><code>curveRed = [
* (0.0000, 0.0000), (0.0667, 0.2864), (0.1333, 0.4007), (0.2000, 0.4845),
@@ -4029,7 +4017,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* (0.5333, 0.7569), (0.6000, 0.7977), (0.6667, 0.8360), (0.7333, 0.8721),
* (0.8000, 0.9063), (0.8667, 0.9389), (0.9333, 0.9701), (1.0000, 1.0000) ]
* </code></pre>
- * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ * <p><img alt="sRGB tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
@@ -4119,9 +4107,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* PRESET_CURVE</p>
* <p>The tonemap curve will be defined by specified standard.</p>
* <p>sRGB (approximated by 16 control points):</p>
- * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ * <p><img alt="sRGB tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
* <p>Rec. 709 (approximated by 16 control points):</p>
- * <p><img alt="Rec. 709 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/rec709_tonemap.png" /></p>
+ * <p><img alt="Rec. 709 tonemapping curve" src="/reference/images/camera2/metadata/android.tonemap.curveRed/rec709_tonemap.png" /></p>
* <p>Note that above figures show a 16 control points approximation of preset
* curves. Camera devices may apply a different approximation to the curve.</p>
* <p><b>Possible values:</b>
diff --git a/android/hardware/display/DisplayManager.java b/android/hardware/display/DisplayManager.java
index ef77d6e6..89357456 100644
--- a/android/hardware/display/DisplayManager.java
+++ b/android/hardware/display/DisplayManager.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.app.KeyguardManager;
import android.content.Context;
import android.graphics.Point;
import android.media.projection.MediaProjection;
@@ -27,7 +28,6 @@ import android.os.Handler;
import android.util.SparseArray;
import android.view.Display;
import android.view.Surface;
-import android.view.WindowManagerPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -254,8 +254,8 @@ public final class DisplayManager {
* </p>
*
* @see #createVirtualDisplay
- * @see WindowManagerPolicy#isKeyguardSecure(int)
- * @see WindowManagerPolicy#isKeyguardTrustedLw()
+ * @see KeyguardManager#isDeviceSecure()
+ * @see KeyguardManager#isDeviceLocked()
* @hide
*/
// TODO: Update name and documentation and un-hide the flag. Don't change the value before that.
diff --git a/android/hardware/display/DisplayManagerInternal.java b/android/hardware/display/DisplayManagerInternal.java
index e845359a..cd551bd4 100644
--- a/android/hardware/display/DisplayManagerInternal.java
+++ b/android/hardware/display/DisplayManagerInternal.java
@@ -174,6 +174,11 @@ public abstract class DisplayManagerInternal {
public abstract boolean isUidPresentOnDisplay(int uid, int displayId);
/**
+ * Persist brightness slider events.
+ */
+ public abstract void persistBrightnessSliderEvents();
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/android/hardware/location/ContextHubClient.java b/android/hardware/location/ContextHubClient.java
index b7e353a4..52527ed6 100644
--- a/android/hardware/location/ContextHubClient.java
+++ b/android/hardware/location/ContextHubClient.java
@@ -16,43 +16,48 @@
package android.hardware.location;
import android.annotation.RequiresPermission;
-import android.os.Handler;
+import android.os.RemoteException;
+
+import dalvik.system.CloseGuard;
import java.io.Closeable;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* A class describing a client of the Context Hub Service.
*
- * Clients can send messages to nanoapps at a Context Hub through this object.
+ * Clients can send messages to nanoapps at a Context Hub through this object. The APIs supported
+ * by this object are thread-safe and can be used without external synchronization.
*
* @hide
*/
public class ContextHubClient implements Closeable {
/*
- * The ContextHubClient interface associated with this client.
+ * The proxy to the client interface at the service.
*/
- // TODO: Implement this interface and associate with ContextHubClient object
- // private final IContextHubClient mClientInterface;
+ private final IContextHubClient mClientProxy;
/*
- * The listening callback associated with this client.
+ * The callback interface associated with this client.
*/
- private ContextHubClientCallback mCallback;
+ private final IContextHubClientCallback mCallbackInterface;
/*
* The Context Hub that this client is attached to.
*/
- private ContextHubInfo mAttachedHub;
+ private final ContextHubInfo mAttachedHub;
- /*
- * The handler to invoke mCallback.
- */
- private Handler mCallbackHandler;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
- ContextHubClient(ContextHubClientCallback callback, Handler handler, ContextHubInfo hubInfo) {
- mCallback = callback;
- mCallbackHandler = handler;
+ private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+ /* package */ ContextHubClient(
+ IContextHubClient clientProxy, IContextHubClientCallback callback,
+ ContextHubInfo hubInfo) {
+ mClientProxy = clientProxy;
+ mCallbackInterface = callback;
mAttachedHub = hubInfo;
+ mCloseGuard.open("close");
}
/**
@@ -71,7 +76,14 @@ public class ContextHubClient implements Closeable {
* All futures messages targeted for this client are dropped at the service.
*/
public void close() {
- throw new UnsupportedOperationException("TODO: Implement this");
+ if (!mIsClosed.getAndSet(true)) {
+ mCloseGuard.close();
+ try {
+ mClientProxy.close();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -90,6 +102,22 @@ public class ContextHubClient implements Closeable {
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
@ContextHubTransaction.Result
public int sendMessageToNanoApp(NanoAppMessage message) {
- throw new UnsupportedOperationException("TODO: Implement this");
+ try {
+ return mClientProxy.sendMessageToNanoApp(message);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ } finally {
+ super.finalize();
+ }
}
}
diff --git a/android/hardware/location/ContextHubInfo.java b/android/hardware/location/ContextHubInfo.java
index aaf6c57b..e1137aa5 100644
--- a/android/hardware/location/ContextHubInfo.java
+++ b/android/hardware/location/ContextHubInfo.java
@@ -16,6 +16,7 @@
package android.hardware.location;
import android.annotation.SystemApi;
+import android.hardware.contexthub.V1_0.ContextHub;
import android.os.Parcel;
import android.os.Parcelable;
@@ -31,22 +32,52 @@ public class ContextHubInfo {
private String mVendor;
private String mToolchain;
private int mPlatformVersion;
- private int mStaticSwVersion;
private int mToolchainVersion;
private float mPeakMips;
private float mStoppedPowerDrawMw;
private float mSleepPowerDrawMw;
private float mPeakPowerDrawMw;
private int mMaxPacketLengthBytes;
+ private byte mChreApiMajorVersion;
+ private byte mChreApiMinorVersion;
+ private short mChrePatchVersion;
+ private long mChrePlatformId;
private int[] mSupportedSensors;
private MemoryRegion[] mMemoryRegions;
+ /*
+ * TODO(b/67734082): Deprecate this constructor and mark private fields as final.
+ */
public ContextHubInfo() {
}
/**
+ * @hide
+ */
+ public ContextHubInfo(ContextHub contextHub) {
+ mId = contextHub.hubId;
+ mName = contextHub.name;
+ mVendor = contextHub.vendor;
+ mToolchain = contextHub.toolchain;
+ mPlatformVersion = contextHub.platformVersion;
+ mToolchainVersion = contextHub.toolchainVersion;
+ mPeakMips = contextHub.peakMips;
+ mStoppedPowerDrawMw = contextHub.stoppedPowerDrawMw;
+ mSleepPowerDrawMw = contextHub.sleepPowerDrawMw;
+ mPeakPowerDrawMw = contextHub.peakPowerDrawMw;
+ mMaxPacketLengthBytes = contextHub.maxSupportedMsgLen;
+ mChrePlatformId = contextHub.chrePlatformId;
+ mChreApiMajorVersion = contextHub.chreApiMajorVersion;
+ mChreApiMinorVersion = contextHub.chreApiMinorVersion;
+ mChrePatchVersion = contextHub.chrePatchVersion;
+
+ mSupportedSensors = new int[0];
+ mMemoryRegions = new MemoryRegion[0];
+ }
+
+ /**
* returns the maximum number of bytes that can be sent per message to the hub
*
* @return int - maximum bytes that can be transmitted in a
@@ -57,17 +88,6 @@ public class ContextHubInfo {
}
/**
- * set the context hub unique identifer
- *
- * @param bytes - Maximum number of bytes per message
- *
- * @hide
- */
- public void setMaxPacketLenBytes(int bytes) {
- mMaxPacketLengthBytes = bytes;
- }
-
- /**
* get the context hub unique identifer
*
* @return int - unique system wide identifier
@@ -77,17 +97,6 @@ public class ContextHubInfo {
}
/**
- * set the context hub unique identifer
- *
- * @param id - unique system wide identifier for the hub
- *
- * @hide
- */
- public void setId(int id) {
- mId = id;
- }
-
- /**
* get a string as a hub name
*
* @return String - a name for the hub
@@ -97,17 +106,6 @@ public class ContextHubInfo {
}
/**
- * set a string as the hub name
- *
- * @param name - the name for the hub
- *
- * @hide
- */
- public void setName(String name) {
- mName = name;
- }
-
- /**
* get a string as the vendor name
*
* @return String - a name for the vendor
@@ -117,17 +115,6 @@ public class ContextHubInfo {
}
/**
- * set a string as the vendor name
- *
- * @param vendor - a name for the vendor
- *
- * @hide
- */
- public void setVendor(String vendor) {
- mVendor = vendor;
- }
-
- /**
* get tool chain string
*
* @return String - description of the tool chain
@@ -137,17 +124,6 @@ public class ContextHubInfo {
}
/**
- * set tool chain string
- *
- * @param toolchain - description of the tool chain
- *
- * @hide
- */
- public void setToolchain(String toolchain) {
- mToolchain = toolchain;
- }
-
- /**
* get platform version
*
* @return int - platform version number
@@ -157,34 +133,12 @@ public class ContextHubInfo {
}
/**
- * set platform version
- *
- * @param platformVersion - platform version number
- *
- * @hide
- */
- public void setPlatformVersion(int platformVersion) {
- mPlatformVersion = platformVersion;
- }
-
- /**
* get static platform version number
*
* @return int - platform version number
*/
public int getStaticSwVersion() {
- return mStaticSwVersion;
- }
-
- /**
- * set platform software version
- *
- * @param staticSwVersion - platform static s/w version number
- *
- * @hide
- */
- public void setStaticSwVersion(int staticSwVersion) {
- mStaticSwVersion = staticSwVersion;
+ return (mChreApiMajorVersion << 24) | (mChreApiMinorVersion << 16) | (mChrePatchVersion);
}
/**
@@ -197,17 +151,6 @@ public class ContextHubInfo {
}
/**
- * set the tool chain version number
- *
- * @param toolchainVersion - tool chain version number
- *
- * @hide
- */
- public void setToolchainVersion(int toolchainVersion) {
- mToolchainVersion = toolchainVersion;
- }
-
- /**
* get the peak processing mips the hub can support
*
* @return float - peak MIPS that this hub can deliver
@@ -217,17 +160,6 @@ public class ContextHubInfo {
}
/**
- * set the peak mips that this hub can support
- *
- * @param peakMips - peak mips this hub can deliver
- *
- * @hide
- */
- public void setPeakMips(float peakMips) {
- mPeakMips = peakMips;
- }
-
- /**
* get the stopped power draw in milliwatts
* This assumes that the hub enter a stopped state - which is
* different from the sleep state. Latencies on exiting the
@@ -241,17 +173,6 @@ public class ContextHubInfo {
}
/**
- * Set the power consumed by the hub in stopped state
- *
- * @param stoppedPowerDrawMw - stopped power in milli watts
- *
- * @hide
- */
- public void setStoppedPowerDrawMw(float stoppedPowerDrawMw) {
- mStoppedPowerDrawMw = stoppedPowerDrawMw;
- }
-
- /**
* get the power draw of the hub in sleep mode. This assumes
* that the hub supports a sleep mode in which the power draw is
* lower than the power consumed when the hub is actively
@@ -267,17 +188,6 @@ public class ContextHubInfo {
}
/**
- * Set the sleep power draw in milliwatts
- *
- * @param sleepPowerDrawMw - sleep power draw in milliwatts.
- *
- * @hide
- */
- public void setSleepPowerDrawMw(float sleepPowerDrawMw) {
- mSleepPowerDrawMw = sleepPowerDrawMw;
- }
-
- /**
* get the peak powe draw of the hub. This is the power consumed
* by the hub at maximum load.
*
@@ -288,18 +198,6 @@ public class ContextHubInfo {
}
/**
- * set the peak power draw of the hub
- *
- * @param peakPowerDrawMw - peak power draw of the hub in
- * milliwatts.
- *
- * @hide
- */
- public void setPeakPowerDrawMw(float peakPowerDrawMw) {
- mPeakPowerDrawMw = peakPowerDrawMw;
- }
-
- /**
* get the sensors supported by this hub
*
* @return int[] - all the supported sensors on this hub
@@ -322,46 +220,65 @@ public class ContextHubInfo {
}
/**
- * set the supported sensors on this hub
- *
- * @param supportedSensors - supported sensors on this hub
+ * @return the CHRE platform ID as defined in chre/version.h
*
+ * TODO(b/67734082): Expose as public API
* @hide
*/
- public void setSupportedSensors(int[] supportedSensors) {
- mSupportedSensors = Arrays.copyOf(supportedSensors, supportedSensors.length);
+ public long getChrePlatformId() {
+ return mChrePlatformId;
}
/**
- * set memory regions for this hub
+ * @return the CHRE API's major version as defined in chre/version.h
*
- * @param memoryRegions - memory regions information
+ * TODO(b/67734082): Expose as public API
+ * @hide
+ */
+ public byte getChreApiMajorVersion() {
+ return mChreApiMajorVersion;
+ }
+
+ /**
+ * @return the CHRE API's minor version as defined in chre/version.h
*
- * @see MemoryRegion
+ * TODO(b/67734082): Expose as public API
+ * @hide
+ */
+ public byte getChreApiMinorVersion() {
+ return mChreApiMinorVersion;
+ }
+
+ /**
+ * @return the CHRE patch version as defined in chre/version.h
*
+ * TODO(b/67734082): Expose as public API
* @hide
*/
- public void setMemoryRegions(MemoryRegion[] memoryRegions) {
- mMemoryRegions = Arrays.copyOf(memoryRegions, memoryRegions.length);
+ public short getChrePatchVersion() {
+ return mChrePatchVersion;
}
@Override
public String toString() {
- String retVal = "";
- retVal += "Id : " + mId;
- retVal += ", Name : " + mName;
- retVal += "\n\tVendor : " + mVendor;
- retVal += ", ToolChain : " + mToolchain;
- retVal += "\n\tPlatformVersion : " + mPlatformVersion;
- retVal += ", StaticSwVersion : " + mStaticSwVersion;
- retVal += "\n\tPeakMips : " + mPeakMips;
- retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW";
- retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW";
- retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes";
- retVal += "\n\tSupported sensors : " + Arrays.toString(mSupportedSensors);
- retVal += "\n\tMemory Regions : " + Arrays.toString(mMemoryRegions);
-
- return retVal;
+ String retVal = "";
+ retVal += "Id : " + mId;
+ retVal += ", Name : " + mName;
+ retVal += "\n\tVendor : " + mVendor;
+ retVal += ", Toolchain : " + mToolchain;
+ retVal += ", Toolchain version: 0x" + Integer.toHexString(mToolchainVersion);
+ retVal += "\n\tPlatformVersion : 0x" + Integer.toHexString(mPlatformVersion);
+ retVal += ", SwVersion : "
+ + mChreApiMajorVersion + "." + mChreApiMinorVersion + "." + mChrePatchVersion;
+ retVal += ", CHRE platform ID: 0x" + Long.toHexString(mChrePlatformId);
+ retVal += "\n\tPeakMips : " + mPeakMips;
+ retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW";
+ retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW";
+ retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes";
+ retVal += "\n\tSupported sensors : " + Arrays.toString(mSupportedSensors);
+ retVal += "\n\tMemory Regions : " + Arrays.toString(mMemoryRegions);
+
+ return retVal;
}
private ContextHubInfo(Parcel in) {
@@ -371,12 +288,15 @@ public class ContextHubInfo {
mToolchain = in.readString();
mPlatformVersion = in.readInt();
mToolchainVersion = in.readInt();
- mStaticSwVersion = in.readInt();
mPeakMips = in.readFloat();
mStoppedPowerDrawMw = in.readFloat();
mSleepPowerDrawMw = in.readFloat();
mPeakPowerDrawMw = in.readFloat();
mMaxPacketLengthBytes = in.readInt();
+ mChrePlatformId = in.readLong();
+ mChreApiMajorVersion = in.readByte();
+ mChreApiMinorVersion = in.readByte();
+ mChrePatchVersion = (short) in.readInt();
int numSupportedSensors = in.readInt();
mSupportedSensors = new int[numSupportedSensors];
@@ -395,12 +315,15 @@ public class ContextHubInfo {
out.writeString(mToolchain);
out.writeInt(mPlatformVersion);
out.writeInt(mToolchainVersion);
- out.writeInt(mStaticSwVersion);
out.writeFloat(mPeakMips);
out.writeFloat(mStoppedPowerDrawMw);
out.writeFloat(mSleepPowerDrawMw);
out.writeFloat(mPeakPowerDrawMw);
out.writeInt(mMaxPacketLengthBytes);
+ out.writeLong(mChrePlatformId);
+ out.writeByte(mChreApiMajorVersion);
+ out.writeByte(mChreApiMinorVersion);
+ out.writeInt(mChrePatchVersion);
out.writeInt(mSupportedSensors.length);
out.writeIntArray(mSupportedSensors);
diff --git a/android/hardware/location/ContextHubManager.java b/android/hardware/location/ContextHubManager.java
index 24117278..b31c7bcd 100644
--- a/android/hardware/location/ContextHubManager.java
+++ b/android/hardware/location/ContextHubManager.java
@@ -456,6 +456,54 @@ public final class ContextHubManager {
}
/**
+ * Creates an interface to the ContextHubClient to send down to the service.
+ *
+ * @param callback the callback to invoke at the client process
+ * @param handler the handler to post callbacks for this client
+ *
+ * @return the callback interface
+ */
+ private IContextHubClientCallback createClientCallback(
+ ContextHubClientCallback callback, Handler handler) {
+ return new IContextHubClientCallback.Stub() {
+ @Override
+ public void onMessageFromNanoApp(NanoAppMessage message) {
+ handler.post(() -> callback.onMessageFromNanoApp(message));
+ }
+
+ @Override
+ public void onHubReset() {
+ handler.post(() -> callback.onHubReset());
+ }
+
+ @Override
+ public void onNanoAppAborted(long nanoAppId, int abortCode) {
+ handler.post(() -> callback.onNanoAppAborted(nanoAppId, abortCode));
+ }
+
+ @Override
+ public void onNanoAppLoaded(long nanoAppId) {
+ handler.post(() -> callback.onNanoAppLoaded(nanoAppId));
+ }
+
+ @Override
+ public void onNanoAppUnloaded(long nanoAppId) {
+ handler.post(() -> callback.onNanoAppUnloaded(nanoAppId));
+ }
+
+ @Override
+ public void onNanoAppEnabled(long nanoAppId) {
+ handler.post(() -> callback.onNanoAppEnabled(nanoAppId));
+ }
+
+ @Override
+ public void onNanoAppDisabled(long nanoAppId) {
+ handler.post(() -> callback.onNanoAppDisabled(nanoAppId));
+ }
+ };
+ }
+
+ /**
* Creates and registers a client and its callback with the Context Hub Service.
*
* A client is registered with the Context Hub Service for a specified Context Hub. When the
@@ -463,19 +511,37 @@ public final class ContextHubManager {
* {@link ContextHubClient} object, and receive notifications through the provided callback.
*
* @param callback the notification callback to register
- * @param hubInfo the hub to attach this client to
- * @param handler the handler to invoke the callback, if null uses the main thread's Looper
- *
+ * @param hubInfo the hub to attach this client to
+ * @param handler the handler to invoke the callback, if null uses the main thread's Looper
* @return the registered client object
*
- * @see ContextHubClientCallback
+ * @throws IllegalArgumentException if hubInfo does not represent a valid hub
+ * @throws IllegalStateException if there were too many registered clients at the service
+ * @throws NullPointerException if callback or hubInfo is null
*
* @hide
+ * @see ContextHubClientCallback
*/
public ContextHubClient createClient(
ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) {
- throw new UnsupportedOperationException(
- "TODO: Implement this, and throw an exception on error");
+ if (callback == null) {
+ throw new NullPointerException("Callback cannot be null");
+ }
+ if (hubInfo == null) {
+ throw new NullPointerException("Hub info cannot be null");
+ }
+
+ Handler realHandler = (handler == null) ? new Handler(mMainLooper) : handler;
+ IContextHubClientCallback clientInterface = createClientCallback(callback, realHandler);
+
+ IContextHubClient client;
+ try {
+ client = mService.createClient(clientInterface, hubInfo.getId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ return new ContextHubClient(client, clientInterface, hubInfo);
}
/**
diff --git a/android/hardware/radio/RadioTuner.java b/android/hardware/radio/RadioTuner.java
index 6e8991aa..e93fd5f1 100644
--- a/android/hardware/radio/RadioTuner.java
+++ b/android/hardware/radio/RadioTuner.java
@@ -309,6 +309,58 @@ public abstract class RadioTuner {
public abstract void setAnalogForced(boolean isForced);
/**
+ * Generic method for setting vendor-specific parameter values.
+ * The framework does not interpret the parameters, they are passed
+ * in an opaque manner between a vendor application and HAL.
+ *
+ * Framework does not make any assumptions on the keys or values, other than
+ * ones stated in VendorKeyValue documentation (a requirement of key
+ * prefixes).
+ *
+ * For each pair in the result map, the key will be one of the keys
+ * contained in the input (possibly with wildcards expanded), and the value
+ * will be a vendor-specific result status (such as "OK" or an error code).
+ * The implementation may choose to return an empty map, or only return
+ * a status for a subset of the provided inputs, at its discretion.
+ *
+ * Application and HAL must not use keys with unknown prefix. In particular,
+ * it must not place a key-value pair in results vector for unknown key from
+ * parameters vector - instead, an unknown key should simply be ignored.
+ * In other words, results vector may contain a subset of parameter keys
+ * (however, the framework doesn't enforce a strict subset - the only
+ * formal requirement is vendor domain prefix for keys).
+ *
+ * @param parameters Vendor-specific key-value pairs.
+ * @return Operation completion status for parameters being set.
+ * @hide FutureFeature
+ */
+ public abstract @NonNull Map<String, String>
+ setParameters(@NonNull Map<String, String> parameters);
+
+ /**
+ * Generic method for retrieving vendor-specific parameter values.
+ * The framework does not interpret the parameters, they are passed
+ * in an opaque manner between a vendor application and HAL.
+ *
+ * Framework does not cache set/get requests, so it's possible for
+ * getParameter to return a different value than previous setParameter call.
+ *
+ * The syntax and semantics of keys are up to the vendor (as long as prefix
+ * rules are obeyed). For instance, vendors may include some form of
+ * wildcard support. In such case, result vector may be of different size
+ * than requested keys vector. However, wildcards are not recognized by
+ * framework and they are passed as-is to the HAL implementation.
+ *
+ * Unknown keys must be ignored and not placed into results vector.
+ *
+ * @param keys Parameter keys to fetch.
+ * @return Vendor-specific key-value pairs.
+ * @hide FutureFeature
+ */
+ public abstract @NonNull Map<String, String>
+ getParameters(@NonNull List<String> keys);
+
+ /**
* Get current antenna connection state for current configuration.
* Only valid if a configuration has been applied.
* @return {@code true} if the antenna is connected, {@code false} otherwise.
@@ -429,6 +481,22 @@ public abstract class RadioTuner {
* Use {@link RadioTuner#getProgramList(String)} to get an actual list.
*/
public void onProgramListChanged() {}
+
+ /**
+ * Generic callback for passing updates to vendor-specific parameter values.
+ * The framework does not interpret the parameters, they are passed
+ * in an opaque manner between a vendor application and HAL.
+ *
+ * It's up to the HAL implementation if and how to implement this callback,
+ * as long as it obeys the prefix rule. In particular, only selected keys
+ * may be notified this way. However, setParameters must not trigger
+ * this callback, while an internal event can change parameters
+ * asynchronously.
+ *
+ * @param parameters Vendor-specific key-value pairs.
+ * @hide FutureFeature
+ */
+ public void onParametersUpdated(@NonNull Map<String, String> parameters) {}
}
}
diff --git a/android/hardware/radio/TunerAdapter.java b/android/hardware/radio/TunerAdapter.java
index b6219690..864d17c2 100644
--- a/android/hardware/radio/TunerAdapter.java
+++ b/android/hardware/radio/TunerAdapter.java
@@ -24,6 +24,7 @@ import android.util.Log;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Implements the RadioTuner interface by forwarding calls to radio service.
@@ -251,6 +252,24 @@ class TunerAdapter extends RadioTuner {
}
@Override
+ public @NonNull Map<String, String> setParameters(@NonNull Map<String, String> parameters) {
+ try {
+ return mTuner.setParameters(Objects.requireNonNull(parameters));
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public @NonNull Map<String, String> getParameters(@NonNull List<String> keys) {
+ try {
+ return mTuner.getParameters(Objects.requireNonNull(keys));
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
public boolean isAntennaConnected() {
try {
return mTuner.isAntennaConnected();
diff --git a/android/hardware/radio/TunerCallbackAdapter.java b/android/hardware/radio/TunerCallbackAdapter.java
index ffd5b30f..a01f658e 100644
--- a/android/hardware/radio/TunerCallbackAdapter.java
+++ b/android/hardware/radio/TunerCallbackAdapter.java
@@ -22,6 +22,8 @@ import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import java.util.Map;
+
/**
* Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback.
*/
@@ -94,4 +96,9 @@ class TunerCallbackAdapter extends ITunerCallback.Stub {
public void onProgramListChanged() {
mHandler.post(() -> mCallback.onProgramListChanged());
}
+
+ @Override
+ public void onParametersUpdated(Map parameters) {
+ mHandler.post(() -> mCallback.onParametersUpdated(parameters));
+ }
}
diff --git a/android/hardware/usb/UsbManager.java b/android/hardware/usb/UsbManager.java
index 6ce96698..bdb90bcc 100644
--- a/android/hardware/usb/UsbManager.java
+++ b/android/hardware/usb/UsbManager.java
@@ -344,7 +344,7 @@ public class UsbManager {
public UsbDeviceConnection openDevice(UsbDevice device) {
try {
String deviceName = device.getDeviceName();
- ParcelFileDescriptor pfd = mService.openDevice(deviceName);
+ ParcelFileDescriptor pfd = mService.openDevice(deviceName, mContext.getPackageName());
if (pfd != null) {
UsbDeviceConnection connection = new UsbDeviceConnection(device);
boolean result = connection.open(deviceName, pfd, mContext);
@@ -400,6 +400,9 @@ public class UsbManager {
* Permission might have been granted temporarily via
* {@link #requestPermission(UsbDevice, PendingIntent)} or
* by the user choosing the caller as the default application for the device.
+ * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that
+ * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they
+ * have additionally the {@link android.Manifest.permission#CAMERA} permission.
*
* @param device to check permissions for
* @return true if caller has permission
@@ -409,7 +412,7 @@ public class UsbManager {
return false;
}
try {
- return mService.hasDevicePermission(device);
+ return mService.hasDevicePermission(device, mContext.getPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -450,6 +453,10 @@ public class UsbManager {
* permission was granted by the user
* </ul>
*
+ * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that
+ * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they
+ * have additionally the {@link android.Manifest.permission#CAMERA} permission.
+ *
* @param device to request permissions for
* @param pi PendingIntent for returning result
*/
diff --git a/android/inputmethodservice/KeyboardView.java b/android/inputmethodservice/KeyboardView.java
index 13b9206b..7836cd09 100644
--- a/android/inputmethodservice/KeyboardView.java
+++ b/android/inputmethodservice/KeyboardView.java
@@ -21,18 +21,16 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Paint.Align;
import android.graphics.PorterDuff;
import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.Paint.Align;
import android.graphics.Region.Op;
+import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.Keyboard.Key;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.GestureDetector;
@@ -986,6 +984,9 @@ public class KeyboardView extends View implements View.OnClickListener {
private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) {
if (mAccessibilityManager.isEnabled()) {
+ if (!mAccessibilityManager.isObservedEventType(eventType)) {
+ return;
+ }
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
onInitializeAccessibilityEvent(event);
final String text;
diff --git a/android/media/ImageReader.java b/android/media/ImageReader.java
index c78c99f7..10195805 100644
--- a/android/media/ImageReader.java
+++ b/android/media/ImageReader.java
@@ -640,7 +640,6 @@ public class ImageReader implements AutoCloseable {
* The ImageReader continues to be usable after this call, but may need to reallocate buffers
* when more buffers are needed for rendering.
* </p>
- * @hide
*/
public void discardFreeBuffers() {
synchronized (mCloseLock) {
diff --git a/android/media/MediaDrm.java b/android/media/MediaDrm.java
index 1feea890..12e5744d 100644
--- a/android/media/MediaDrm.java
+++ b/android/media/MediaDrm.java
@@ -91,10 +91,10 @@ import android.util.Log;
* are only decrypted when the samples are delivered to the decoder.
* <p>
* MediaDrm methods throw {@link android.media.MediaDrm.MediaDrmStateException}
- * when a method is called on a MediaDrm object that has had an unrecoverable failure
- * in the DRM plugin or security hardware.
- * {@link android.media.MediaDrm.MediaDrmStateException} extends
- * {@link java.lang.IllegalStateException} with the addition of a developer-readable
+ * when a method is called on a MediaDrm object that has had an unrecoverable failure
+ * in the DRM plugin or security hardware.
+ * {@link android.media.MediaDrm.MediaDrmStateException} extends
+ * {@link java.lang.IllegalStateException} with the addition of a developer-readable
* diagnostic information string associated with the exception.
* <p>
* In the event of a mediaserver process crash or restart while a MediaDrm object
@@ -102,9 +102,9 @@ import android.util.Log;
* To recover, the app must release the MediaDrm object, then create and initialize
* a new one.
* <p>
- * As {@link android.media.MediaDrmResetException} and
- * {@link android.media.MediaDrm.MediaDrmStateException} both extend
- * {@link java.lang.IllegalStateException}, they should be in an earlier catch()
+ * As {@link android.media.MediaDrmResetException} and
+ * {@link android.media.MediaDrm.MediaDrmStateException} both extend
+ * {@link java.lang.IllegalStateException}, they should be in an earlier catch()
* block than {@link java.lang.IllegalStateException} if handled separately.
* <p>
* <a name="Callbacks"></a>
@@ -165,7 +165,7 @@ public final class MediaDrm {
/**
* Query if the given scheme identified by its UUID is supported on
- * this device, and whether the drm plugin is able to handle the
+ * this device, and whether the DRM plugin is able to handle the
* media container format specified by mimeType.
* @param uuid The UUID of the crypto scheme.
* @param mimeType The MIME type of the media container, e.g. "video/mp4"
@@ -745,7 +745,7 @@ public final class MediaDrm {
* returned in KeyRequest.defaultUrl.
* <p>
* After the app has received the key request response from the server,
- * it should deliver to the response to the DRM engine plugin using the method
+ * it should deliver to the response to the MediaDrm instance using the method
* {@link #provideKeyResponse}.
*
* @param scope may be a sessionId or a keySetId, depending on the specified keyType.
@@ -781,7 +781,7 @@ public final class MediaDrm {
/**
* A key response is received from the license server by the app, then it is
- * provided to the DRM engine plugin using provideKeyResponse. When the
+ * provided to the MediaDrm instance using provideKeyResponse. When the
* response is for an offline key request, a keySetId is returned that can be
* used to later restore the keys to a new session with the method
* {@link #restoreKeys}.
@@ -829,7 +829,7 @@ public final class MediaDrm {
* in the form of {name, value} pairs. Since DRM license policies vary by vendor,
* the specific status field names are determined by each DRM vendor. Refer to your
* DRM provider documentation for definitions of the field names for a particular
- * DRM engine plugin.
+ * DRM plugin.
*
* @param sessionId the session ID for the DRM session
*/
@@ -897,11 +897,11 @@ public final class MediaDrm {
@NonNull String certAuthority);
/**
- * After a provision response is received by the app, it is provided to the DRM
- * engine plugin using this method.
+ * After a provision response is received by the app, it is provided to the
+ * MediaDrm instance using this method.
*
* @param response the opaque provisioning response byte array to provide to the
- * DRM engine plugin.
+ * MediaDrm instance.
*
* @throws DeniedByServerException if the response indicates that the
* server rejected the request
@@ -912,7 +912,6 @@ public final class MediaDrm {
}
@NonNull
- /* could there be a valid response with 0-sized certificate or key? */
private native Certificate provideProvisionResponseNative(@NonNull byte[] response)
throws DeniedByServerException;
@@ -953,26 +952,26 @@ public final class MediaDrm {
/**
* Remove all secure stops without requiring interaction with the server.
*/
- public native void releaseAllSecureStops();
+ public native void releaseAllSecureStops();
/**
- * String property name: identifies the maker of the DRM engine plugin
+ * String property name: identifies the maker of the DRM plugin
*/
public static final String PROPERTY_VENDOR = "vendor";
/**
- * String property name: identifies the version of the DRM engine plugin
+ * String property name: identifies the version of the DRM plugin
*/
public static final String PROPERTY_VERSION = "version";
/**
- * String property name: describes the DRM engine plugin
+ * String property name: describes the DRM plugin
*/
public static final String PROPERTY_DESCRIPTION = "description";
/**
* String property name: a comma-separated list of cipher and mac algorithms
- * supported by CryptoSession. The list may be empty if the DRM engine
+ * supported by CryptoSession. The list may be empty if the DRM
* plugin does not support CryptoSession operations.
*/
public static final String PROPERTY_ALGORITHMS = "algorithms";
@@ -988,7 +987,7 @@ public final class MediaDrm {
public @interface StringProperty {}
/**
- * Read a DRM engine plugin String property value, given the property name string.
+ * Read a MediaDrm String property value, given the property name string.
* <p>
* Standard fields names are:
* {@link #PROPERTY_VENDOR}, {@link #PROPERTY_VERSION},
@@ -998,6 +997,13 @@ public final class MediaDrm {
public native String getPropertyString(@NonNull @StringProperty String propertyName);
/**
+ * Set a MediaDrm String property value, given the property name string
+ * and new value for the property.
+ */
+ public native void setPropertyString(@NonNull @StringProperty String propertyName,
+ @NonNull String value);
+
+ /**
* Byte array property name: the device unique identifier is established during
* device provisioning and provides a means of uniquely identifying each device.
*/
@@ -1011,7 +1017,7 @@ public final class MediaDrm {
public @interface ArrayProperty {}
/**
- * Read a DRM engine plugin byte array property value, given the property name string.
+ * Read a MediaDrm byte array property value, given the property name string.
* <p>
* Standard fields names are {@link #PROPERTY_DEVICE_UNIQUE_ID}
*/
@@ -1019,17 +1025,13 @@ public final class MediaDrm {
public native byte[] getPropertyByteArray(@ArrayProperty String propertyName);
/**
- * Set a DRM engine plugin String property value.
- */
- public native void setPropertyString(
- String propertyName, @NonNull String value);
-
- /**
- * Set a DRM engine plugin byte array property value.
- */
- public native void setPropertyByteArray(
+ * Set a MediaDrm byte array property value, given the property name string
+ * and new value for the property.
+ */
+ public native void setPropertyByteArray(@NonNull @ArrayProperty
String propertyName, @NonNull byte[] value);
+
private static final native void setCipherAlgorithmNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId, @NonNull String algorithm);
@@ -1158,7 +1160,7 @@ public final class MediaDrm {
* The algorithm string conforms to JCA Standard Names for Mac
* Algorithms and is case insensitive. For example "HmacSHA256".
* <p>
- * The list of supported algorithms for a DRM engine plugin can be obtained
+ * The list of supported algorithms for a DRM plugin can be obtained
* using the method {@link #getPropertyString} with the property name
* "algorithms".
*/
@@ -1272,7 +1274,7 @@ public final class MediaDrm {
* storage, and used when invoking the signRSA method.
*
* @param response the opaque certificate response byte array to provide to the
- * DRM engine plugin.
+ * MediaDrm instance.
*
* @throws DeniedByServerException if the response indicates that the
* server rejected the request
diff --git a/android/media/MediaFormat.java b/android/media/MediaFormat.java
index c475e122..306ed83c 100644
--- a/android/media/MediaFormat.java
+++ b/android/media/MediaFormat.java
@@ -721,14 +721,16 @@ public final class MediaFormat {
/**
* A key for boolean DEFAULT behavior for the track. The track with DEFAULT=true is
* selected in the absence of a specific user choice.
- * This is currently only used for subtitle tracks, when the user selected
- * 'Default' for the captioning locale.
+ * This is currently used in two scenarios:
+ * 1) for subtitle tracks, when the user selected 'Default' for the captioning locale.
+ * 2) for a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track, indicating the image is the
+ * primary item in the file.
+
* The associated value is an integer, where non-0 means TRUE. This is an optional
* field; if not specified, DEFAULT is considered to be FALSE.
*/
public static final String KEY_IS_DEFAULT = "is-default";
-
/**
* A key for the FORCED field for subtitle tracks. True if it is a
* forced subtitle track. Forced subtitle tracks are essential for the
diff --git a/android/media/MediaMetadata.java b/android/media/MediaMetadata.java
index bdc0fda6..31eb948d 100644
--- a/android/media/MediaMetadata.java
+++ b/android/media/MediaMetadata.java
@@ -34,6 +34,7 @@ import android.util.SparseArray;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Set;
+import java.util.Objects;
/**
* Contains metadata about an item, such as the title, artist, etc.
@@ -616,6 +617,71 @@ public final class MediaMetadata implements Parcelable {
};
/**
+ * Compares the contents of this object to another MediaMetadata object. It
+ * does not compare Bitmaps and Ratings as the media player can choose to
+ * forgo these fields depending on how you retrieve the MediaMetadata.
+ *
+ * @param o The Metadata object to compare this object against
+ * @return Whether or not the two objects have matching fields (excluding
+ * Bitmaps and Ratings)
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof MediaMetadata)) {
+ return false;
+ }
+
+ final MediaMetadata m = (MediaMetadata) o;
+
+ for (int i = 0; i < METADATA_KEYS_TYPE.size(); i++) {
+ String key = METADATA_KEYS_TYPE.keyAt(i);
+ switch (METADATA_KEYS_TYPE.valueAt(i)) {
+ case METADATA_TYPE_TEXT:
+ if (!Objects.equals(getString(key), m.getString(key))) {
+ return false;
+ }
+ break;
+ case METADATA_TYPE_LONG:
+ if (getLong(key) != m.getLong(key)) {
+ return false;
+ }
+ break;
+ default:
+ // Ignore ratings and bitmaps when comparing
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 17;
+
+ for (int i = 0; i < METADATA_KEYS_TYPE.size(); i++) {
+ String key = METADATA_KEYS_TYPE.keyAt(i);
+ switch (METADATA_KEYS_TYPE.valueAt(i)) {
+ case METADATA_TYPE_TEXT:
+ hashCode = 31 * hashCode + Objects.hash(getString(key));
+ break;
+ case METADATA_TYPE_LONG:
+ hashCode = 31 * hashCode + Long.hashCode(getLong(key));
+ break;
+ default:
+ // Ignore ratings and bitmaps when comparing
+ break;
+ }
+ }
+
+ return hashCode;
+ }
+
+ /**
* Use to build MediaMetadata objects. The system defined metadata keys must
* use the appropriate data type.
*/
diff --git a/android/media/MediaMuxer.java b/android/media/MediaMuxer.java
index 91e57ee0..02c71b28 100644
--- a/android/media/MediaMuxer.java
+++ b/android/media/MediaMuxer.java
@@ -258,12 +258,18 @@ final public class MediaMuxer {
* in include/media/stagefright/MediaMuxer.h!
*/
private OutputFormat() {}
+ /** @hide */
+ public static final int MUXER_OUTPUT_FIRST = 0;
/** MPEG4 media file format*/
- public static final int MUXER_OUTPUT_MPEG_4 = 0;
+ public static final int MUXER_OUTPUT_MPEG_4 = MUXER_OUTPUT_FIRST;
/** WEBM media file format*/
- public static final int MUXER_OUTPUT_WEBM = 1;
+ public static final int MUXER_OUTPUT_WEBM = MUXER_OUTPUT_FIRST + 1;
/** 3GPP media file format*/
- public static final int MUXER_OUTPUT_3GPP = 2;
+ public static final int MUXER_OUTPUT_3GPP = MUXER_OUTPUT_FIRST + 2;
+ /** HEIF media file format*/
+ public static final int MUXER_OUTPUT_HEIF = MUXER_OUTPUT_FIRST + 3;
+ /** @hide */
+ public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_HEIF;
};
/** @hide */
@@ -271,6 +277,7 @@ final public class MediaMuxer {
OutputFormat.MUXER_OUTPUT_MPEG_4,
OutputFormat.MUXER_OUTPUT_WEBM,
OutputFormat.MUXER_OUTPUT_3GPP,
+ OutputFormat.MUXER_OUTPUT_HEIF,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Format {}
@@ -347,8 +354,7 @@ final public class MediaMuxer {
}
private void setUpMediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException {
- if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 && format != OutputFormat.MUXER_OUTPUT_WEBM
- && format != OutputFormat.MUXER_OUTPUT_3GPP) {
+ if (format < OutputFormat.MUXER_OUTPUT_FIRST || format > OutputFormat.MUXER_OUTPUT_LAST) {
throw new IllegalArgumentException("format: " + format + " is invalid");
}
mNativeObject = nativeSetup(fd, format);
diff --git a/android/media/MediaRecorder.java b/android/media/MediaRecorder.java
index 76784904..3c49b80b 100644
--- a/android/media/MediaRecorder.java
+++ b/android/media/MediaRecorder.java
@@ -25,6 +25,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
+import android.util.ArrayMap;
import android.util.Log;
import android.view.Surface;
@@ -34,6 +35,8 @@ import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
+import com.android.internal.annotations.GuardedBy;
+
/**
* Used to record audio and video. The recording control is based on a
* simple state machine (see below).
@@ -76,7 +79,7 @@ import java.lang.ref.WeakReference;
* <a href="{@docRoot}guide/topics/media/audio-capture.html">Audio Capture</a> developer guide.</p>
* </div>
*/
-public class MediaRecorder
+public class MediaRecorder implements AudioRouting
{
static {
System.loadLibrary("media_jni");
@@ -1243,6 +1246,7 @@ public class MediaRecorder
private static final int MEDIA_RECORDER_TRACK_EVENT_INFO = 101;
private static final int MEDIA_RECORDER_TRACK_EVENT_LIST_END = 1000;
+ private static final int MEDIA_RECORDER_AUDIO_ROUTING_CHANGED = 10000;
@Override
public void handleMessage(Message msg) {
@@ -1265,6 +1269,16 @@ public class MediaRecorder
return;
+ case MEDIA_RECORDER_AUDIO_ROUTING_CHANGED:
+ AudioManager.resetAudioPortGeneration();
+ synchronized (mRoutingChangeListeners) {
+ for (NativeRoutingEventHandlerDelegate delegate
+ : mRoutingChangeListeners.values()) {
+ delegate.notifyClient();
+ }
+ }
+ return;
+
default:
Log.e(TAG, "Unknown message type " + msg.what);
return;
@@ -1272,6 +1286,155 @@ public class MediaRecorder
}
}
+ //--------------------------------------------------------------------------
+ // Explicit Routing
+ //--------------------
+ private AudioDeviceInfo mPreferredDevice = null;
+
+ /**
+ * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
+ * the input from this MediaRecorder.
+ * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio source.
+ * If deviceInfo is null, default routing is restored.
+ * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
+ * does not correspond to a valid audio input device.
+ */
+ @Override
+ public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
+ if (deviceInfo != null && !deviceInfo.isSource()) {
+ return false;
+ }
+ int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0;
+ boolean status = native_setInputDevice(preferredDeviceId);
+ if (status == true) {
+ synchronized (this) {
+ mPreferredDevice = deviceInfo;
+ }
+ }
+ return status;
+ }
+
+ /**
+ * Returns the selected input device specified by {@link #setPreferredDevice}. Note that this
+ * is not guaranteed to correspond to the actual device being used for recording.
+ */
+ @Override
+ public AudioDeviceInfo getPreferredDevice() {
+ synchronized (this) {
+ return mPreferredDevice;
+ }
+ }
+
+ /**
+ * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaRecorder
+ * Note: The query is only valid if the MediaRecorder is currently recording.
+ * If the recorder is not recording, the returned device can be null or correspond to previously
+ * selected device when the recorder was last active.
+ */
+ @Override
+ public AudioDeviceInfo getRoutedDevice() {
+ int deviceId = native_getRoutedDeviceId();
+ if (deviceId == 0) {
+ return null;
+ }
+ AudioDeviceInfo[] devices =
+ AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_INPUTS);
+ for (int i = 0; i < devices.length; i++) {
+ if (devices[i].getId() == deviceId) {
+ return devices[i];
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
+ */
+ private void enableNativeRoutingCallbacksLocked(boolean enabled) {
+ if (mRoutingChangeListeners.size() == 0) {
+ native_enableDeviceCallback(enabled);
+ }
+ }
+
+ /**
+ * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
+ * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)}
+ * by an app to receive (re)routing notifications.
+ */
+ @GuardedBy("mRoutingChangeListeners")
+ private ArrayMap<AudioRouting.OnRoutingChangedListener,
+ NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>();
+
+ /**
+ * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+ * changes on this MediaRecorder.
+ * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+ * notifications of rerouting events.
+ * @param handler Specifies the {@link Handler} object for the thread on which to execute
+ * the callback. If <code>null</code>, the handler on the main looper will be used.
+ */
+ @Override
+ public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
+ Handler handler) {
+ synchronized (mRoutingChangeListeners) {
+ if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
+ enableNativeRoutingCallbacksLocked(true);
+ mRoutingChangeListeners.put(
+ listener, new NativeRoutingEventHandlerDelegate(this, listener, handler));
+ }
+ }
+ }
+
+ /**
+ * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+ * to receive rerouting notifications.
+ * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+ * to remove.
+ */
+ @Override
+ public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
+ synchronized (mRoutingChangeListeners) {
+ if (mRoutingChangeListeners.containsKey(listener)) {
+ mRoutingChangeListeners.remove(listener);
+ enableNativeRoutingCallbacksLocked(false);
+ }
+ }
+ }
+
+ /**
+ * Helper class to handle the forwarding of native events to the appropriate listener
+ * (potentially) handled in a different thread
+ */
+ private class NativeRoutingEventHandlerDelegate {
+ private MediaRecorder mMediaRecorder;
+ private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener;
+ private Handler mHandler;
+
+ NativeRoutingEventHandlerDelegate(final MediaRecorder mediaRecorder,
+ final AudioRouting.OnRoutingChangedListener listener, Handler handler) {
+ mMediaRecorder = mediaRecorder;
+ mOnRoutingChangedListener = listener;
+ mHandler = handler != null ? handler : mEventHandler;
+ }
+
+ void notifyClient() {
+ if (mHandler != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mOnRoutingChangedListener != null) {
+ mOnRoutingChangedListener.onRoutingChanged(mMediaRecorder);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private native final boolean native_setInputDevice(int deviceId);
+ private native final int native_getRoutedDeviceId();
+ private native final void native_enableDeviceCallback(boolean enabled);
+
/**
* Called from native code when an interesting event happens. This method
* just uses the EventHandler system to post the event back to the main app thread.
diff --git a/android/media/tv/TvInputManager.java b/android/media/tv/TvInputManager.java
index fd1f2cf6..143182f8 100644
--- a/android/media/tv/TvInputManager.java
+++ b/android/media/tv/TvInputManager.java
@@ -1330,6 +1330,7 @@ public final class TvInputManager {
*
* @return the list of content ratings blocked by the user.
*/
+ @SystemApi
public List<TvContentRating> getBlockedRatings() {
try {
List<TvContentRating> ratings = new ArrayList<>();
@@ -1585,8 +1586,10 @@ public final class TvInputManager {
* @param info The TV input which will use the acquired Hardware.
* @return Hardware on success, {@code null} otherwise.
*
+ * @hide
* @removed
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
public Hardware acquireTvInputHardware(int deviceId, final HardwareCallback callback,
TvInputInfo info) {
@@ -2591,6 +2594,7 @@ public final class TvInputManager {
}
/** @removed */
+ @SystemApi
public boolean dispatchKeyEventToHdmi(KeyEvent event) {
return false;
}
diff --git a/android/net/IpSecAlgorithm.java b/android/net/IpSecAlgorithm.java
index 64f8f39e..d6e62cf1 100644
--- a/android/net/IpSecAlgorithm.java
+++ b/android/net/IpSecAlgorithm.java
@@ -15,6 +15,7 @@
*/
package android.net;
+import android.annotation.NonNull;
import android.annotation.StringDef;
import android.os.Build;
import android.os.Parcel;
@@ -27,8 +28,10 @@ import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
- * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to
- * RFC 4301.
+ * This class represents a single algorithm that can be used by an {@link IpSecTransform}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
*/
public final class IpSecAlgorithm implements Parcelable {
/**
@@ -39,16 +42,16 @@ public final class IpSecAlgorithm implements Parcelable {
public static final String CRYPT_AES_CBC = "cbc(aes)";
/**
- * MD5 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in new
- * applications and is provided for legacy compatibility with 3gpp infrastructure.
+ * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+ * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
*
* <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128.
*/
public static final String AUTH_HMAC_MD5 = "hmac(md5)";
/**
- * SHA1 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in
- * new applications and is provided for legacy compatibility with 3gpp infrastructure.
+ * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+ * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
*
* <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160.
*/
@@ -69,7 +72,7 @@ public final class IpSecAlgorithm implements Parcelable {
public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
/**
- * SHA512 HMAC Authentication/Integrity Algorithm
+ * SHA512 HMAC Authentication/Integrity Algorithm.
*
* <p>Valid truncation lengths are multiples of 8 bits from 256 to (default) 512.
*/
@@ -80,9 +83,9 @@ public final class IpSecAlgorithm implements Parcelable {
*
* <p>Valid lengths for keying material are {160, 224, 288}.
*
- * <p>As per RFC4106 (Section 8.1), keying material consists of a 128, 192, or 256 bit AES key
- * followed by a 32-bit salt. RFC compliance requires that the salt must be unique per
- * invocation with the same key.
+ * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section
+ * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
+ * salt. RFC compliance requires that the salt must be unique per invocation with the same key.
*
* <p>Valid ICV (truncation) lengths are {64, 96, 128}.
*/
@@ -105,48 +108,47 @@ public final class IpSecAlgorithm implements Parcelable {
private final int mTruncLenBits;
/**
- * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the
- * algorithm
+ * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+ * defined as constants in this class.
*
- * @param algorithm type for IpSec.
- * @param key non-null Key padded to a multiple of 8 bits.
+ * @param algorithm name of the algorithm.
+ * @param key key padded to a multiple of 8 bits.
*/
- public IpSecAlgorithm(String algorithm, byte[] key) {
+ public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key) {
this(algorithm, key, key.length * 8);
}
/**
- * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the
- * algorithm
+ * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+ * defined as constants in this class.
+ *
+ * <p>This constructor only supports algorithms that use a truncation length. i.e.
+ * Authentication and Authenticated Encryption algorithms.
*
- * @param algoName precise name of the algorithm to be used.
- * @param key non-null Key padded to a multiple of 8 bits.
- * @param truncLenBits the number of bits of output hash to use; only meaningful for
- * Authentication or Authenticated Encryption (equivalent to ICV length).
+ * @param algorithm name of the algorithm.
+ * @param key key padded to a multiple of 8 bits.
+ * @param truncLenBits number of bits of output hash to use.
*/
- public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) {
- if (!isTruncationLengthValid(algoName, truncLenBits)) {
+ public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) {
+ if (!isTruncationLengthValid(algorithm, truncLenBits)) {
throw new IllegalArgumentException("Unknown algorithm or invalid length");
}
- mName = algoName;
+ mName = algorithm;
mKey = key.clone();
mTruncLenBits = Math.min(truncLenBits, key.length * 8);
}
- /** Retrieve the algorithm name */
+ /** Get the algorithm name */
public String getName() {
return mName;
}
- /** Retrieve the key for this algorithm */
+ /** Get the key for this algorithm */
public byte[] getKey() {
return mKey.clone();
}
- /**
- * Retrieve the truncation length, in bits, for the key in this algo. By default this will be
- * the length in bits of the key.
- */
+ /** Get the truncation length of this algorithm, in bits */
public int getTruncationLengthBits() {
return mTruncLenBits;
}
diff --git a/android/net/IpSecConfig.java b/android/net/IpSecConfig.java
index 61b13a92..e6cd3fc1 100644
--- a/android/net/IpSecConfig.java
+++ b/android/net/IpSecConfig.java
@@ -20,7 +20,12 @@ import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
-/** @hide */
+/**
+ * This class encapsulates all the configuration parameters needed to create IPsec transforms and
+ * policies.
+ *
+ * @hide
+ */
public final class IpSecConfig implements Parcelable {
private static final String TAG = "IpSecConfig";
@@ -38,6 +43,9 @@ public final class IpSecConfig implements Parcelable {
// for outbound packets. It may also be used to select packets.
private Network mNetwork;
+ /**
+ * This class captures the parameters that specifically apply to inbound or outbound traffic.
+ */
public static class Flow {
// Minimum requirements for identifying a transform
// SPI identifying the IPsec flow in packet processing
diff --git a/android/net/IpSecManager.java b/android/net/IpSecManager.java
index eccd5f47..a9e60ec8 100644
--- a/android/net/IpSecManager.java
+++ b/android/net/IpSecManager.java
@@ -19,6 +19,7 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.NonNull;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.Context;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
@@ -37,22 +38,28 @@ import java.net.InetAddress;
import java.net.Socket;
/**
- * This class contains methods for managing IPsec sessions, which will perform kernel-space
- * encryption and decryption of socket or Network traffic.
+ * This class contains methods for managing IPsec sessions. Once configured, the kernel will apply
+ * confidentiality (encryption) and integrity (authentication) to IP traffic.
*
- * <p>An IpSecManager may be obtained by calling {@link
- * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link
- * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE}
+ * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
+ * transport mode security associations and apply them to individual sockets. Applications looking
+ * to create a VPN should use {@link VpnService}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
*/
@SystemService(Context.IPSEC_SERVICE)
public final class IpSecManager {
private static final String TAG = "IpSecManager";
/**
- * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index.
+ * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
*
* <p>No IPsec packet may contain an SPI of 0.
+ *
+ * @hide
*/
+ @TestApi
public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
/** @hide */
@@ -66,10 +73,12 @@ public final class IpSecManager {
public static final int INVALID_RESOURCE_ID = 0;
/**
- * Indicates that the combination of remote InetAddress and SPI was non-unique for a given
- * request. If encountered, selection of a new SPI is required before a transform may be
- * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random
- * or reserved using reserveSecurityParameterIndex.
+ * Thrown to indicate that a requested SPI is in use.
+ *
+ * <p>The combination of remote {@code InetAddress} and SPI must be unique across all apps on
+ * one device. If this error is encountered, a new SPI is required before a transform may be
+ * created. This error can be avoided by calling {@link
+ * IpSecManager#reserveSecurityParameterIndex}.
*/
public static final class SpiUnavailableException extends AndroidException {
private final int mSpi;
@@ -78,24 +87,26 @@ public final class IpSecManager {
* Construct an exception indicating that a transform with the given SPI is already in use
* or otherwise unavailable.
*
- * @param msg Description indicating the colliding SPI
+ * @param msg description indicating the colliding SPI
* @param spi the SPI that could not be used due to a collision
*/
SpiUnavailableException(String msg, int spi) {
- super(msg + "(spi: " + spi + ")");
+ super(msg + " (spi: " + spi + ")");
mSpi = spi;
}
- /** Retrieve the SPI that caused a collision */
+ /** Get the SPI that caused a collision. */
public int getSpi() {
return mSpi;
}
}
/**
- * Indicates that the requested system resource for IPsec, such as a socket or other system
- * resource is unavailable. If this exception is thrown, try releasing allocated objects of the
- * type requested.
+ * Thrown to indicate that an IPsec resource is unavailable.
+ *
+ * <p>This could apply to resources such as sockets, {@link SecurityParameterIndex}, {@link
+ * IpSecTransform}, or other system resources. If this exception is thrown, users should release
+ * allocated objects of the type requested.
*/
public static final class ResourceUnavailableException extends AndroidException {
@@ -106,6 +117,13 @@ public final class IpSecManager {
private final IIpSecService mService;
+ /**
+ * This class represents a reserved SPI.
+ *
+ * <p>Objects of this type are used to track reserved security parameter indices. They can be
+ * obtained by calling {@link IpSecManager#reserveSecurityParameterIndex} and must be released
+ * by calling {@link #close()} when they are no longer needed.
+ */
public static final class SecurityParameterIndex implements AutoCloseable {
private final IIpSecService mService;
private final InetAddress mRemoteAddress;
@@ -113,7 +131,7 @@ public final class IpSecManager {
private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
private int mResourceId;
- /** Return the underlying SPI held by this object */
+ /** Get the underlying SPI held by this object. */
public int getSpi() {
return mSpi;
}
@@ -135,6 +153,7 @@ public final class IpSecManager {
mCloseGuard.close();
}
+ /** Check that the SPI was closed properly. */
@Override
protected void finalize() throws Throwable {
if (mCloseGuard != null) {
@@ -197,13 +216,13 @@ public final class IpSecManager {
}
/**
- * Reserve an SPI for traffic bound towards the specified remote address.
+ * Reserve a random SPI for traffic bound to or from the specified remote address.
*
* <p>If successful, this SPI is guaranteed available until released by a call to {@link
* SecurityParameterIndex#close()}.
*
* @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
- * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
+ * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress
* @return the reserved SecurityParameterIndex
* @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
* for this user
@@ -223,17 +242,18 @@ public final class IpSecManager {
}
/**
- * Reserve an SPI for traffic bound towards the specified remote address.
+ * Reserve the requested SPI for traffic bound to or from the specified remote address.
*
* <p>If successful, this SPI is guaranteed available until released by a call to {@link
* SecurityParameterIndex#close()}.
*
* @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
- * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
- * @param requestedSpi the requested SPI, or '0' to allocate a random SPI.
+ * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress
+ * @param requestedSpi the requested SPI, or '0' to allocate a random SPI
* @return the reserved SecurityParameterIndex
* @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
* for this user
+ * @throws SpiUnavailableException indicating that the requested SPI could not be reserved
*/
public SecurityParameterIndex reserveSecurityParameterIndex(
int direction, InetAddress remoteAddress, int requestedSpi)
@@ -245,16 +265,28 @@ public final class IpSecManager {
}
/**
- * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
- * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
- * transform. For security reasons, attempts to send traffic to any IP address other than the
- * address associated with that transform will throw an IOException. In addition, if the
- * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
- * send() or receive() until the transform is removed from the socket by calling {@link
- * #removeTransportModeTransform(Socket, IpSecTransform)};
+ * Apply an IPsec transform to a stream socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+ *
+ * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform
+ * will be removed. However, inbound traffic on the old transform will continue to be decrypted
+ * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap
+ * allows rekey procedures where both transforms are valid until both endpoints are using the
+ * new transform and all in-flight packets have been received.
*
* @param socket a stream socket
- * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
* @hide
*/
public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
@@ -265,16 +297,28 @@ public final class IpSecManager {
}
/**
- * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec
- * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
- * transform. For security reasons, attempts to send traffic to any IP address other than the
- * address associated with that transform will throw an IOException. In addition, if the
- * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
- * send() or receive() until the transform is removed from the socket by calling {@link
- * #removeTransportModeTransform(DatagramSocket, IpSecTransform)};
+ * Apply an IPsec transform to a datagram socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+ *
+ * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform
+ * will be removed. However, inbound traffic on the old transform will continue to be decrypted
+ * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap
+ * allows rekey procedures where both transforms are valid until both endpoints are using the
+ * new transform and all in-flight packets have been received.
*
* @param socket a datagram socket
- * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
* @hide
*/
public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
@@ -285,16 +329,28 @@ public final class IpSecManager {
}
/**
- * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
- * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
- * transform. For security reasons, attempts to send traffic to any IP address other than the
- * address associated with that transform will throw an IOException. In addition, if the
- * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
- * send() or receive() until the transform is removed from the socket by calling {@link
- * #removeTransportModeTransform(FileDescriptor, IpSecTransform)};
+ * Apply an IPsec transform to a socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+ *
+ * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform
+ * will be removed. However, inbound traffic on the old transform will continue to be decrypted
+ * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap
+ * allows rekey procedures where both transforms are valid until both endpoints are using the
+ * new transform and all in-flight packets have been received.
*
* @param socket a socket file descriptor
- * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
*/
public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
throws IOException {
@@ -323,6 +379,7 @@ public final class IpSecManager {
* Applications should probably not use this API directly. Instead, they should use {@link
* VpnService} to provide VPN capability in a more generic fashion.
*
+ * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked.
* @param net a {@link Network} that will be tunneled via IP Sec.
* @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform.
* @hide
@@ -330,14 +387,19 @@ public final class IpSecManager {
public void applyTunnelModeTransform(Network net, IpSecTransform transform) {}
/**
- * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
- * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
- * communication in the clear in the event socket reuse is desired. This operation will succeed
- * regardless of the underlying state of a transform. If a transform is removed, communication
- * on all sockets to which that transform was applied will fail until this method is called.
+ * Remove an IPsec transform from a stream socket.
+ *
+ * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed
+ * regardless of the state of the transform. Removing a transform from a socket allows the
+ * socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
*
- * @param socket a socket that previously had a transform applied to it.
+ * @param socket a socket that previously had a transform applied to it
* @param transform the IPsec Transform that was previously applied to the given socket
+ * @throws IOException indicating that the transform could not be removed from the socket
* @hide
*/
public void removeTransportModeTransform(Socket socket, IpSecTransform transform)
@@ -348,14 +410,19 @@ public final class IpSecManager {
}
/**
- * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not
- * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
- * communication in the clear in the event socket reuse is desired. This operation will succeed
- * regardless of the underlying state of a transform. If a transform is removed, communication
- * on all sockets to which that transform was applied will fail until this method is called.
+ * Remove an IPsec transform from a datagram socket.
*
- * @param socket a socket that previously had a transform applied to it.
+ * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed
+ * regardless of the state of the transform. Removing a transform from a socket allows the
+ * socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
* @param transform the IPsec Transform that was previously applied to the given socket
+ * @throws IOException indicating that the transform could not be removed from the socket
* @hide
*/
public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
@@ -366,14 +433,19 @@ public final class IpSecManager {
}
/**
- * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
- * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
- * communication in the clear in the event socket reuse is desired. This operation will succeed
- * regardless of the underlying state of a transform. If a transform is removed, communication
- * on all sockets to which that transform was applied will fail until this method is called.
+ * Remove an IPsec transform from a socket.
+ *
+ * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed
+ * regardless of the state of the transform. Removing a transform from a socket allows the
+ * socket to be reused for communication in the clear.
*
- * @param socket a socket file descriptor that previously had a transform applied to it.
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
* @param transform the IPsec Transform that was previously applied to the given socket
+ * @throws IOException indicating that the transform could not be removed from the socket
*/
public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
throws IOException {
@@ -382,7 +454,7 @@ public final class IpSecManager {
}
}
- /* Call down to activate a transform */
+ /* Call down to remove a transform */
private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
try {
mService.removeTransportModeTransform(pfd, transform.getResourceId());
@@ -397,6 +469,7 @@ public final class IpSecManager {
* all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is
* lost, all traffic will drop.
*
+ * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked.
* @param net a network that currently has transform applied to it.
* @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given
* network
@@ -405,11 +478,18 @@ public final class IpSecManager {
public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
/**
- * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for
- * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic.
+ * This class provides access to a UDP encapsulation Socket.
*
- * <p>The socket provided by this class cannot be re-bound or closed via the inner
- * FileDescriptor. Instead, disposing of this socket requires a call to close().
+ * <p>{@code UdpEncapsulationSocket} wraps a system-provided datagram socket intended for IKEv2
+ * signalling and UDP encapsulated IPsec traffic. Instances can be obtained by calling {@link
+ * IpSecManager#openUdpEncapsulationSocket}. The provided socket cannot be re-bound by the
+ * caller. The caller should not close the {@code FileDescriptor} returned by {@link
+ * #getSocket}, but should use {@link #close} instead.
+ *
+ * <p>Allowing the user to close or unbind a UDP encapsulation socket could impact the traffic
+ * of the next user who binds to that port. To prevent this scenario, these sockets are held
+ * open by the system so that they may only be closed by calling {@link #close} or when the user
+ * process exits.
*/
public static final class UdpEncapsulationSocket implements AutoCloseable {
private final ParcelFileDescriptor mPfd;
@@ -443,7 +523,7 @@ public final class IpSecManager {
mCloseGuard.open("constructor");
}
- /** Access the inner UDP Encapsulation Socket */
+ /** Get the wrapped socket. */
public FileDescriptor getSocket() {
if (mPfd == null) {
return null;
@@ -451,22 +531,19 @@ public final class IpSecManager {
return mPfd.getFileDescriptor();
}
- /** Retrieve the port number of the inner encapsulation socket */
+ /** Get the bound port of the wrapped socket. */
public int getPort() {
return mPort;
}
- @Override
/**
- * Release the resources that have been reserved for this Socket.
+ * Close this socket.
*
- * <p>This method closes the underlying socket, reducing a user's allocated sockets in the
- * system. This must be done as part of cleanup following use of a socket. Failure to do so
- * will cause the socket to count against a total allocation limit for IpSec and eventually
- * fail due to resource limits.
- *
- * @param fd a file descriptor previously returned as a UDP Encapsulation socket.
+ * <p>This closes the wrapped socket. Open encapsulation sockets count against a user's
+ * resource limits, and forgetting to close them eventually will result in {@link
+ * ResourceUnavailableException} being thrown.
*/
+ @Override
public void close() throws IOException {
try {
mService.closeUdpEncapsulationSocket(mResourceId);
@@ -483,6 +560,7 @@ public final class IpSecManager {
mCloseGuard.close();
}
+ /** Check that the socket was closed properly. */
@Override
protected void finalize() throws Throwable {
if (mCloseGuard != null) {
@@ -499,21 +577,14 @@ public final class IpSecManager {
};
/**
- * Open a socket that is bound to a free UDP port on the system.
- *
- * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
- * the caller. This provides safe access to a socket on a port that can later be used as a UDP
- * Encapsulation port.
+ * Open a socket for UDP encapsulation and bind to the given port.
*
- * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
- * socket port. Explicitly opening this port is only necessary if communication is desired on
- * that port.
+ * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
*
- * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this
- * method will bind to the specified port or fail. To retrieve the port number, call {@link
- * android.system.Os#getsockname(FileDescriptor)}.
- * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime
- * of the object.
+ * @param port a local UDP port
+ * @return a socket that is bound to the given port
+ * @throws IOException indicating that the socket could not be opened or bound
+ * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
*/
// Returning a socket in this fashion that has been created and bound by the system
// is the only safe way to ensure that a socket is both accessible to the user and
@@ -533,17 +604,16 @@ public final class IpSecManager {
}
/**
- * Open a socket that is bound to a port selected by the system.
+ * Open a socket for UDP encapsulation.
*
- * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
- * the caller. This provides safe access to a socket on a port that can later be used as a UDP
- * Encapsulation port.
+ * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
*
- * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
- * socket port. Explicitly opening this port is only necessary if communication is desired on
- * that port.
+ * <p>The local port of the returned socket can be obtained by calling {@link
+ * UdpEncapsulationSocket#getPort()}.
*
- * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port
+ * @return a socket that is bound to a local port
+ * @throws IOException indicating that the socket could not be opened or bound
+ * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
*/
// Returning a socket in this fashion that has been created and bound by the system
// is the only safe way to ensure that a socket is both accessible to the user and
@@ -556,7 +626,7 @@ public final class IpSecManager {
}
/**
- * Retrieve an instance of an IpSecManager within you application context
+ * Construct an instance of IpSecManager within an application context.
*
* @param context the application context for this manager
* @hide
diff --git a/android/net/IpSecTransform.java b/android/net/IpSecTransform.java
index 48b5bd5c..cda4ec76 100644
--- a/android/net/IpSecTransform.java
+++ b/android/net/IpSecTransform.java
@@ -38,27 +38,29 @@ import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
/**
- * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec.
+ * This class represents an IPsec transform, which comprises security associations in one or both
+ * directions.
*
- * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout
- * the lifetime of the underlying transform. If a transform object leaves scope, the underlying
- * transform may be disabled automatically, with likely undesirable results.
+ * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform}
+ * object encapsulates the properties and state of an inbound and outbound IPsec security
+ * association. That includes, but is not limited to, algorithm choice, key material, and allocated
+ * system resources.
*
- * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array
- * of traffic or may represent a transport mode transform operating on a Socket or Sockets.
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
*/
public final class IpSecTransform implements AutoCloseable {
private static final String TAG = "IpSecTransform";
/**
- * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
- * to traffic towards the host.
+ * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
+ * applies to traffic towards the host.
*/
public static final int DIRECTION_IN = 0;
/**
- * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
- * to traffic from the host.
+ * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
+ * applies to traffic from the host.
*/
public static final int DIRECTION_OUT = 1;
@@ -77,16 +79,16 @@ public final class IpSecTransform implements AutoCloseable {
public static final int ENCAP_NONE = 0;
/**
- * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad
- * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP.
+ * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP
+ * header and payload. This prevents traffic from being interpreted as ESP or IKEv2.
*
* @hide
*/
public static final int ENCAP_ESPINUDP_NON_IKE = 1;
/**
- * IpSec traffic will be encapsulated within UDP as per <a
- * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>.
+ * IPsec traffic will be encapsulated within UDP as per
+ * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>.
*
* @hide
*/
@@ -165,13 +167,14 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Deactivate an IpSecTransform and free all resources for that transform that are managed by
- * the system for this Transform.
+ * Deactivate this {@code IpSecTransform} and free allocated resources.
*
- * <p>Deactivating a transform while it is still applied to any Socket will result in sockets
- * refusing to send or receive data. This method will silently succeed if the specified
- * transform has already been removed; thus, it is always safe to attempt cleanup when a
- * transform is no longer needed.
+ * <p>Deactivating a transform while it is still applied to a socket will result in errors on
+ * that socket. Make sure to remove transforms by calling {@link
+ * IpSecManager#removeTransportModeTransform}. Note, removing an {@code IpSecTransform} from a
+ * socket will not deactivate it (because one transform may be applied to multiple sockets).
+ *
+ * <p>It is safe to call this method on a transform that has already been deactivated.
*/
public void close() {
Log.d(TAG, "Removing Transform with Id " + mResourceId);
@@ -197,6 +200,7 @@ public final class IpSecTransform implements AutoCloseable {
}
}
+ /** Check that the transform was closed properly. */
@Override
protected void finalize() throws Throwable {
if (mCloseGuard != null) {
@@ -264,65 +268,63 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Builder object to facilitate the creation of IpSecTransform objects.
- *
- * <p>Apply additional properties to the transform and then call a build() method to return an
- * IpSecTransform object.
- *
- * @see Builder#buildTransportModeTransform(InetAddress)
+ * This class is used to build {@link IpSecTransform} objects.
*/
public static class Builder {
private Context mContext;
private IpSecConfig mConfig;
/**
- * Add an encryption algorithm to the transform for the given direction.
+ * Set the encryption algorithm for the given direction.
*
- * <p>If encryption is set for a given direction without also providing an SPI for that
- * direction, creation of an IpSecTransform will fail upon calling a build() method.
+ * <p>If encryption is set for a direction without also providing an SPI for that direction,
+ * creation of an {@code IpSecTransform} will fail when attempting to build the transform.
*
- * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ * <p>Encryption is mutually exclusive with authenticated encryption.
*
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
*/
public IpSecTransform.Builder setEncryption(
@TransformDirection int direction, IpSecAlgorithm algo) {
+ // TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
mConfig.setEncryption(direction, algo);
return this;
}
/**
- * Add an authentication/integrity algorithm to the transform.
+ * Set the authentication (integrity) algorithm for the given direction.
*
- * <p>If authentication is set for a given direction without also providing an SPI for that
- * direction, creation of an IpSecTransform will fail upon calling a build() method.
+ * <p>If authentication is set for a direction without also providing an SPI for that
+ * direction, creation of an {@code IpSecTransform} will fail when attempting to build the
+ * transform.
*
- * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ * <p>Authentication is mutually exclusive with authenticated encryption.
*
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
*/
public IpSecTransform.Builder setAuthentication(
@TransformDirection int direction, IpSecAlgorithm algo) {
+ // TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
mConfig.setAuthentication(direction, algo);
return this;
}
/**
- * Add an authenticated encryption algorithm to the transform for the given direction.
+ * Set the authenticated encryption algorithm for the given direction.
*
* <p>If an authenticated encryption algorithm is set for a given direction without also
- * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling
- * a build() method.
+ * providing an SPI for that direction, creation of an {@code IpSecTransform} will fail when
+ * attempting to build the transform.
*
* <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated
* Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as
- * referred to in RFC 4301)
+ * referred to in <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
*
* <p>Authenticated encryption is mutually exclusive with encryption and authentication.
*
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
* be applied.
*/
@@ -333,19 +335,16 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
- * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
- * given destination address.
+ * Set the SPI for the given direction.
*
- * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
- * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int,
- * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being
- * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}.
+ * <p>Because IPsec operates at the IP layer, this 32-bit identifier uniquely identifies
+ * packets to a given destination address. To prevent SPI collisions, values should be
+ * reserved by calling {@link IpSecManager#reserveSecurityParameterIndex}.
*
- * <p>Unless an SPI is set for a given direction, traffic in that direction will be
- * sent/received without any IPsec applied.
+ * <p>If the SPI and algorithms are omitted for one direction, traffic in that direction
+ * will not be encrypted or authenticated.
*
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
* @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
* traffic
*/
@@ -356,11 +355,10 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Specify the network on which this transform will emit its traffic; (otherwise it will
- * emit on the default network).
+ * Set the {@link Network} which will carry tunneled traffic.
*
- * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in
- * tunnel mode.
+ * <p>Restricts the transformed traffic to a particular {@link Network}. This is required
+ * for tunnel mode, otherwise tunneled traffic would be sent on the default network.
*
* @hide
*/
@@ -371,15 +369,18 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Add UDP encapsulation to an IPv4 transform
+ * Add UDP encapsulation to an IPv4 transform.
*
- * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for
- * details on how UDP should be applied to IPsec.
+ * <p>This allows IPsec traffic to pass through a NAT.
*
- * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and
- * receiving encapsulating traffic.
- * @param remotePort the UDP port number of the remote that will send and receive
- * encapsulated traffic. In the case of IKE, this is likely port 4500.
+ * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec
+ * ESP Packets</a>
+ * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23,
+ * NAT Traversal of IKEv2</a>
+ *
+ * @param localSocket a socket for sending and receiving encapsulated traffic
+ * @param remotePort the UDP port number of the remote host that will send and receive
+ * encapsulated traffic. In the case of IKEv2, this should be port 4500.
*/
public IpSecTransform.Builder setIpv4Encapsulation(
IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
@@ -393,12 +394,15 @@ public final class IpSecTransform implements AutoCloseable {
// TODO: Probably a better exception to throw for NATTKeepalive failure
// TODO: Specify the needed NATT keepalive permission.
/**
- * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded
- * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot
- * be activated, then the transform will fail to activate and throw an IOException.
+ * Set NAT-T keepalives to be sent with a given interval.
+ *
+ * <p>This will set power-efficient keepalive packets to be sent by the system. If NAT-T
+ * keepalive is requested but cannot be activated, then creation of an {@link
+ * IpSecTransform} will fail when calling the build method.
+ *
+ * @param intervalSeconds the maximum number of seconds between keepalive packets. Must be
+ * between 20s and 3600s.
*
- * @param intervalSeconds the maximum number of seconds between keepalive packets, no less
- * than 20s and no more than 3600s.
* @hide
*/
@SystemApi
@@ -408,36 +412,29 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform.
- * Some parameters have interdependencies that are checked at build time. If a well-formed
- * transform cannot be created from the supplied parameters, this method will throw an
- * Exception.
+ * Build a transport mode {@link IpSecTransform}.
*
- * <p>Upon a successful return from this call, the provided IpSecTransform will be active
- * and may be applied to sockets. If too many IpSecTransform objects are active for a given
- * user this operation will fail and throw ResourceUnavailableException. To avoid these
- * exceptions, unused Transform objects must be cleaned up by calling {@link
- * IpSecTransform#close()} when they are no longer needed.
+ * <p>This builds and activates a transport mode transform. Note that an active transform
+ * will not affect any network traffic until it has been applied to one or more sockets.
*
- * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this
- * socket will cause the transform to be applied.
- * <p>Note that an active transform will not impact any network traffic until it has
- * been applied to one or more Sockets. Calling this method is a necessary precondition
- * for applying it to a socket, but is not sufficient to actually apply IPsec.
+ * @see IpSecManager#applyTransportModeTransform
+ *
+ * @param remoteAddress the remote {@code InetAddress} of traffic on sockets that will use
+ * this transform
* @throws IllegalArgumentException indicating that a particular combination of transform
- * properties is invalid.
- * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms
- * may be allocated
- * @throws SpiUnavailableException if the SPI collides with an existing transform
- * (unlikely).
- * @throws ResourceUnavailableException if the current user currently has exceeded the
- * number of allowed active transforms.
+ * properties is invalid
+ * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms are
+ * active
+ * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
+ * collides with an existing transform
+ * @throws IOException indicating other errors
*/
public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
throws IpSecManager.ResourceUnavailableException,
IpSecManager.SpiUnavailableException, IOException {
mConfig.setMode(MODE_TRANSPORT);
mConfig.setRemoteAddress(remoteAddress.getHostAddress());
+ // FIXME: modifying a builder after calling build can change the built transform.
return new IpSecTransform(mContext, mConfig).activate();
}
@@ -465,9 +462,9 @@ public final class IpSecTransform implements AutoCloseable {
}
/**
- * Create a new IpSecTransform.Builder to construct an IpSecTransform
+ * Create a new IpSecTransform.Builder.
*
- * @param context current Context
+ * @param context current context
*/
public Builder(@NonNull Context context) {
Preconditions.checkNotNull(context);
diff --git a/android/net/NetworkCapabilities.java b/android/net/NetworkCapabilities.java
index ee75fd44..f468e5d2 100644
--- a/android/net/NetworkCapabilities.java
+++ b/android/net/NetworkCapabilities.java
@@ -31,16 +31,10 @@ import java.util.Objects;
import java.util.StringJoiner;
/**
- * Representation of the capabilities of a network. This object serves two
- * purposes:
- * <ul>
- * <li>An expression of the current capabilities of an active network, typically
- * expressed through
+ * Representation of the capabilities of an active network. Instances are
+ * typically obtained through
* {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)}
* or {@link ConnectivityManager#getNetworkCapabilities(Network)}.
- * <li>An expression of the future capabilities of a desired network, typically
- * expressed through {@link NetworkRequest}.
- * </ul>
* <p>
* This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of
* network selection. Rather than indicate a need for Wi-Fi because an
@@ -79,7 +73,7 @@ public final class NetworkCapabilities implements Parcelable {
*/
public void clearAll() {
mNetworkCapabilities = mTransportTypes = 0;
- mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = 0;
+ mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
mNetworkSpecifier = null;
mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
}
@@ -359,6 +353,7 @@ public final class NetworkCapabilities implements Parcelable {
/**
* Sets all the capabilities set on this {@code NetworkCapability} instance.
+ * This overwrites any existing capabilities.
*
* @hide
*/
@@ -582,6 +577,7 @@ public final class NetworkCapabilities implements Parcelable {
/**
* Sets all the transports set on this {@code NetworkCapability} instance.
+ * This overwrites any existing transports.
*
* @hide
*/
@@ -780,7 +776,7 @@ public final class NetworkCapabilities implements Parcelable {
* Signal strength. This is a signed integer, and higher values indicate better signal.
* The exact units are bearer-dependent. For example, Wi-Fi uses RSSI.
*/
- private int mSignalStrength;
+ private int mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
/**
* Sets the signal strength. This is a signed integer, with higher values indicating a stronger
diff --git a/android/net/NetworkRequest.java b/android/net/NetworkRequest.java
index 25b17052..97ded2d7 100644
--- a/android/net/NetworkRequest.java
+++ b/android/net/NetworkRequest.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -32,7 +33,7 @@ public class NetworkRequest implements Parcelable {
* The {@link NetworkCapabilities} that define this request.
* @hide
*/
- public final NetworkCapabilities networkCapabilities;
+ public final @NonNull NetworkCapabilities networkCapabilities;
/**
* Identifies the request. NetworkRequests should only be constructed by
@@ -307,7 +308,7 @@ public class NetworkRequest implements Parcelable {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(networkCapabilities, flags);
+ networkCapabilities.writeToParcel(dest, flags);
dest.writeInt(legacyType);
dest.writeInt(requestId);
dest.writeString(type.name());
@@ -315,7 +316,7 @@ public class NetworkRequest implements Parcelable {
public static final Creator<NetworkRequest> CREATOR =
new Creator<NetworkRequest>() {
public NetworkRequest createFromParcel(Parcel in) {
- NetworkCapabilities nc = (NetworkCapabilities)in.readParcelable(null);
+ NetworkCapabilities nc = NetworkCapabilities.CREATOR.createFromParcel(in);
int legacyType = in.readInt();
int requestId = in.readInt();
Type type = Type.valueOf(in.readString()); // IllegalArgumentException if invalid.
diff --git a/android/net/NetworkState.java b/android/net/NetworkState.java
index 95e3802e..b00cb482 100644
--- a/android/net/NetworkState.java
+++ b/android/net/NetworkState.java
@@ -18,6 +18,7 @@ package android.net;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Slog;
/**
* Snapshot of network state.
@@ -43,6 +44,16 @@ public class NetworkState implements Parcelable {
this.network = network;
this.subscriberId = subscriberId;
this.networkId = networkId;
+
+ // This object is an atomic view of a network, so the various components
+ // should always agree on roaming state.
+ if (networkInfo != null && networkCapabilities != null) {
+ if (networkInfo.isRoaming() == networkCapabilities
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) {
+ Slog.wtf("NetworkState", "Roaming state disagreement between " + networkInfo
+ + " and " + networkCapabilities);
+ }
+ }
}
public NetworkState(Parcel in) {
diff --git a/android/net/metrics/WakeupEvent.java b/android/net/metrics/WakeupEvent.java
index 8f1a5c42..af9a73ca 100644
--- a/android/net/metrics/WakeupEvent.java
+++ b/android/net/metrics/WakeupEvent.java
@@ -29,7 +29,7 @@ public class WakeupEvent {
public String iface;
public int uid;
public int ethertype;
- public byte[] dstHwAddr;
+ public MacAddress dstHwAddr;
public String srcIp;
public String dstIp;
public int ipNextHeader;
@@ -44,7 +44,7 @@ public class WakeupEvent {
j.add(iface);
j.add("uid: " + Integer.toString(uid));
j.add("eth=0x" + Integer.toHexString(ethertype));
- j.add("dstHw=" + MacAddress.stringAddrFromByteAddr(dstHwAddr));
+ j.add("dstHw=" + dstHwAddr);
if (ipNextHeader > 0) {
j.add("ipNxtHdr=" + ipNextHeader);
j.add("srcIp=" + srcIp);
diff --git a/android/net/metrics/WakeupStats.java b/android/net/metrics/WakeupStats.java
index 1ba97771..23c1f20f 100644
--- a/android/net/metrics/WakeupStats.java
+++ b/android/net/metrics/WakeupStats.java
@@ -16,7 +16,6 @@
package android.net.metrics;
-import android.net.MacAddress;
import android.os.Process;
import android.os.SystemClock;
import android.util.SparseIntArray;
@@ -80,7 +79,7 @@ public class WakeupStats {
break;
}
- switch (MacAddress.macAddressType(ev.dstHwAddr)) {
+ switch (ev.dstHwAddr.addressType()) {
case UNICAST:
l2UnicastCount++;
break;
diff --git a/android/net/wifi/BatchedScanResult.java b/android/net/wifi/BatchedScanResult.java
index 6d9f00f3..c06543ec 100644
--- a/android/net/wifi/BatchedScanResult.java
+++ b/android/net/wifi/BatchedScanResult.java
@@ -17,6 +17,7 @@
package android.net.wifi;
import android.os.Parcelable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import java.util.ArrayList;
@@ -29,6 +30,7 @@ import java.util.List;
* @removed
*/
@Deprecated
+@SystemApi
public class BatchedScanResult implements Parcelable {
private static final String TAG = "BatchedScanResult";
diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java
index 66fabf33..558004ce 100644
--- a/android/net/wifi/WifiManager.java
+++ b/android/net/wifi/WifiManager.java
@@ -33,6 +33,8 @@ import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.ProvisioningCallback;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -1128,7 +1130,7 @@ public class WifiManager {
*/
private int addOrUpdateNetwork(WifiConfiguration config) {
try {
- return mService.addOrUpdateNetwork(config, mContext.getOpPackageName());
+ return mService.addOrUpdateNetwork(config);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1149,7 +1151,7 @@ public class WifiManager {
*/
public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
try {
- if (!mService.addOrUpdatePasspointConfiguration(config, mContext.getOpPackageName())) {
+ if (!mService.addOrUpdatePasspointConfiguration(config)) {
throw new IllegalArgumentException();
}
} catch (RemoteException e) {
@@ -1166,7 +1168,7 @@ public class WifiManager {
*/
public void removePasspointConfiguration(String fqdn) {
try {
- if (!mService.removePasspointConfiguration(fqdn, mContext.getOpPackageName())) {
+ if (!mService.removePasspointConfiguration(fqdn)) {
throw new IllegalArgumentException();
}
} catch (RemoteException e) {
@@ -1252,7 +1254,7 @@ public class WifiManager {
*/
public boolean removeNetwork(int netId) {
try {
- return mService.removeNetwork(netId, mContext.getOpPackageName());
+ return mService.removeNetwork(netId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1298,7 +1300,7 @@ public class WifiManager {
boolean success;
try {
- success = mService.enableNetwork(netId, attemptConnect, mContext.getOpPackageName());
+ success = mService.enableNetwork(netId, attemptConnect);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1324,7 +1326,7 @@ public class WifiManager {
*/
public boolean disableNetwork(int netId) {
try {
- return mService.disableNetwork(netId, mContext.getOpPackageName());
+ return mService.disableNetwork(netId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1337,7 +1339,7 @@ public class WifiManager {
*/
public boolean disconnect() {
try {
- mService.disconnect(mContext.getOpPackageName());
+ mService.disconnect();
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1352,7 +1354,7 @@ public class WifiManager {
*/
public boolean reconnect() {
try {
- mService.reconnect(mContext.getOpPackageName());
+ mService.reconnect();
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1367,7 +1369,7 @@ public class WifiManager {
*/
public boolean reassociate() {
try {
- mService.reassociate(mContext.getOpPackageName());
+ mService.reassociate();
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1740,7 +1742,7 @@ public class WifiManager {
@Deprecated
public boolean saveConfiguration() {
try {
- return mService.saveConfiguration(mContext.getOpPackageName());
+ return mService.saveConfiguration();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2056,7 +2058,7 @@ public class WifiManager {
}
mLOHSCallbackProxy = null;
try {
- mService.stopLocalOnlyHotspot(mContext.getOpPackageName());
+ mService.stopLocalOnlyHotspot();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2175,7 +2177,7 @@ public class WifiManager {
@RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) {
try {
- mService.setWifiApConfiguration(wifiConfig, mContext.getOpPackageName());
+ mService.setWifiApConfiguration(wifiConfig);
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -2947,7 +2949,7 @@ public class WifiManager {
public void disableEphemeralNetwork(String SSID) {
if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
try {
- mService.disableEphemeralNetwork(SSID, mContext.getOpPackageName());
+ mService.disableEphemeralNetwork(SSID);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2986,7 +2988,7 @@ public class WifiManager {
*/
public Messenger getWifiServiceMessenger() {
try {
- return mService.getWifiServiceMessenger(mContext.getOpPackageName());
+ return mService.getWifiServiceMessenger();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3516,7 +3518,7 @@ public class WifiManager {
*/
public void factoryReset() {
try {
- mService.factoryReset(mContext.getOpPackageName());
+ mService.factoryReset();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3543,7 +3545,7 @@ public class WifiManager {
*/
public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
try {
- return mService.setEnableAutoJoinWhenAssociated(enabled, mContext.getOpPackageName());
+ return mService.setEnableAutoJoinWhenAssociated(enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3610,4 +3612,45 @@ public class WifiManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Start subscription provisioning flow
+ * @param provider {@link OsuProvider} to provision with
+ * @param callback {@link ProvisioningCallback} for updates regarding provisioning flow
+ * @hide
+ */
+ public void startSubscriptionProvisioning(OsuProvider provider, ProvisioningCallback callback,
+ @Nullable Handler handler) {
+ Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
+ try {
+ mService.startSubscriptionProvisioning(provider,
+ new ProvisioningCallbackProxy(looper, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static class ProvisioningCallbackProxy extends IProvisioningCallback.Stub {
+ private final Handler mHandler;
+ private final ProvisioningCallback mCallback;
+
+ ProvisioningCallbackProxy(Looper looper, ProvisioningCallback callback) {
+ mHandler = new Handler(looper);
+ mCallback = callback;
+ }
+
+ @Override
+ public void onProvisioningStatus(int status) {
+ mHandler.post(() -> {
+ mCallback.onProvisioningStatus(status);
+ });
+ }
+
+ @Override
+ public void onProvisioningFailure(int status) {
+ mHandler.post(() -> {
+ mCallback.onProvisioningFailure(status);
+ });
+ }
+ }
}
diff --git a/android/net/wifi/aware/DiscoverySessionCallback.java b/android/net/wifi/aware/DiscoverySessionCallback.java
index 115b86d1..aa2c268c 100644
--- a/android/net/wifi/aware/DiscoverySessionCallback.java
+++ b/android/net/wifi/aware/DiscoverySessionCallback.java
@@ -113,6 +113,32 @@ public class DiscoverySessionCallback {
}
/**
+ * Called when a discovery (publish or subscribe) operation results in a
+ * service discovery. Called when a Subscribe service was configured with a range requirement
+ * {@link SubscribeConfig.Builder#setMinDistanceMm(int)} and/or
+ * {@link SubscribeConfig.Builder#setMaxDistanceMm(int)}. A discovery will only be declared
+ * (i.e. this callback called) if the range of the publisher is within the specified distance
+ * constraints.
+ *
+ * @param peerHandle An opaque handle to the peer matching our discovery operation.
+ * @param serviceSpecificInfo The service specific information (arbitrary
+ * byte array) provided by the peer as part of its discovery
+ * configuration.
+ * @param matchFilter The filter which resulted in this service discovery. For
+ * {@link PublishConfig#PUBLISH_TYPE_UNSOLICITED},
+ * {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE} discovery sessions this is the publisher's
+ * match filter. For {@link PublishConfig#PUBLISH_TYPE_SOLICITED},
+ * {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} discovery sessions this
+ * is the subscriber's match filter.
+ * @param distanceMm The measured distance to the Publisher in mm.
+ * @hide
+ */
+ public void onServiceDiscoveredWithinRange(PeerHandle peerHandle,
+ byte[] serviceSpecificInfo, List<byte[]> matchFilter, int distanceMm) {
+ /* empty */
+ }
+
+ /**
* Called in response to
* {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])}
* when a message is transmitted successfully - i.e. when it was received successfully by the
diff --git a/android/net/wifi/aware/PublishConfig.java b/android/net/wifi/aware/PublishConfig.java
index d0186207..e60f52f8 100644
--- a/android/net/wifi/aware/PublishConfig.java
+++ b/android/net/wifi/aware/PublishConfig.java
@@ -29,6 +29,7 @@ import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
/**
* Defines the configuration of a Aware publish session. Built using
@@ -81,14 +82,19 @@ public final class PublishConfig implements Parcelable {
public final boolean mEnableTerminateNotification;
/** @hide */
+ public final boolean mEnableRanging;
+
+ /** @hide */
public PublishConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
- int publishType, int ttlSec, boolean enableTerminateNotification) {
+ int publishType, int ttlSec, boolean enableTerminateNotification,
+ boolean enableRanging) {
mServiceName = serviceName;
mServiceSpecificInfo = serviceSpecificInfo;
mMatchFilter = matchFilter;
mPublishType = publishType;
mTtlSec = ttlSec;
mEnableTerminateNotification = enableTerminateNotification;
+ mEnableRanging = enableRanging;
}
@Override
@@ -103,7 +109,8 @@ public final class PublishConfig implements Parcelable {
+ (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString()
+ ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length)
+ ", mPublishType=" + mPublishType + ", mTtlSec=" + mTtlSec
- + ", mEnableTerminateNotification=" + mEnableTerminateNotification + "]";
+ + ", mEnableTerminateNotification=" + mEnableTerminateNotification
+ + ", mEnableRanging=" + mEnableRanging + "]";
}
@Override
@@ -119,6 +126,7 @@ public final class PublishConfig implements Parcelable {
dest.writeInt(mPublishType);
dest.writeInt(mTtlSec);
dest.writeInt(mEnableTerminateNotification ? 1 : 0);
+ dest.writeInt(mEnableRanging ? 1 : 0);
}
public static final Creator<PublishConfig> CREATOR = new Creator<PublishConfig>() {
@@ -135,9 +143,10 @@ public final class PublishConfig implements Parcelable {
int publishType = in.readInt();
int ttlSec = in.readInt();
boolean enableTerminateNotification = in.readInt() != 0;
+ boolean enableRanging = in.readInt() != 0;
return new PublishConfig(serviceName, ssi, matchFilter, publishType,
- ttlSec, enableTerminateNotification);
+ ttlSec, enableTerminateNotification, enableRanging);
}
};
@@ -157,21 +166,14 @@ public final class PublishConfig implements Parcelable {
lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, lhs.mMatchFilter)
&& mPublishType == lhs.mPublishType
&& mTtlSec == lhs.mTtlSec
- && mEnableTerminateNotification == lhs.mEnableTerminateNotification;
+ && mEnableTerminateNotification == lhs.mEnableTerminateNotification
+ && mEnableRanging == lhs.mEnableRanging;
}
@Override
public int hashCode() {
- int result = 17;
-
- result = 31 * result + Arrays.hashCode(mServiceName);
- result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
- result = 31 * result + Arrays.hashCode(mMatchFilter);
- result = 31 * result + mPublishType;
- result = 31 * result + mTtlSec;
- result = 31 * result + (mEnableTerminateNotification ? 1 : 0);
-
- return result;
+ return Objects.hash(mServiceName, mServiceSpecificInfo, mMatchFilter, mPublishType, mTtlSec,
+ mEnableTerminateNotification, mEnableRanging);
}
/**
@@ -226,6 +228,7 @@ public final class PublishConfig implements Parcelable {
private int mPublishType = PUBLISH_TYPE_UNSOLICITED;
private int mTtlSec = 0;
private boolean mEnableTerminateNotification = true;
+ private boolean mEnableRanging = false;
/**
* Specify the service name of the publish session. The actual on-air
@@ -352,12 +355,35 @@ public final class PublishConfig implements Parcelable {
}
/**
+ * Configure whether the publish discovery session supports ranging and allows peers to
+ * measure distance to it. This API is used in conjunction with
+ * {@link SubscribeConfig.Builder#setMinDistanceMm(int)} and
+ * {@link SubscribeConfig.Builder#setMaxDistanceMm(int)} to specify a minimum and/or
+ * maximum distance at which discovery will be triggered.
+ * <p>
+ * Optional. Disabled by default - i.e. any peer which attempts to measure distance to this
+ * device will be refused. If the peer has ranging enabled (using the
+ * {@link SubscribeConfig} APIs listed above, it will never discover this device.
+ *
+ * @param enable If true, ranging is supported on request of the peer.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ *
+ * @hide
+ */
+ public Builder setRangingEnabled(boolean enable) {
+ mEnableRanging = enable;
+ return this;
+ }
+
+ /**
* Build {@link PublishConfig} given the current requests made on the
* builder.
*/
public PublishConfig build() {
return new PublishConfig(mServiceName, mServiceSpecificInfo, mMatchFilter, mPublishType,
- mTtlSec, mEnableTerminateNotification);
+ mTtlSec, mEnableTerminateNotification, mEnableRanging);
}
}
}
diff --git a/android/net/wifi/aware/SubscribeConfig.java b/android/net/wifi/aware/SubscribeConfig.java
index 4bf2fb6a..f6552a76 100644
--- a/android/net/wifi/aware/SubscribeConfig.java
+++ b/android/net/wifi/aware/SubscribeConfig.java
@@ -29,6 +29,7 @@ import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
/**
* Defines the configuration of a Aware subscribe session. Built using
@@ -79,15 +80,32 @@ public final class SubscribeConfig implements Parcelable {
public final boolean mEnableTerminateNotification;
/** @hide */
+ public final boolean mMinDistanceMmSet;
+
+ /** @hide */
+ public final int mMinDistanceMm;
+
+ /** @hide */
+ public final boolean mMaxDistanceMmSet;
+
+ /** @hide */
+ public final int mMaxDistanceMm;
+
+ /** @hide */
public SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
- int subscribeType, int ttlSec,
- boolean enableTerminateNotification) {
+ int subscribeType, int ttlSec, boolean enableTerminateNotification,
+ boolean minDistanceMmSet, int minDistanceMm, boolean maxDistanceMmSet,
+ int maxDistanceMm) {
mServiceName = serviceName;
mServiceSpecificInfo = serviceSpecificInfo;
mMatchFilter = matchFilter;
mSubscribeType = subscribeType;
mTtlSec = ttlSec;
mEnableTerminateNotification = enableTerminateNotification;
+ mMinDistanceMm = minDistanceMm;
+ mMinDistanceMmSet = minDistanceMmSet;
+ mMaxDistanceMm = maxDistanceMm;
+ mMaxDistanceMmSet = maxDistanceMmSet;
}
@Override
@@ -102,7 +120,11 @@ public final class SubscribeConfig implements Parcelable {
+ (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString()
+ ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length)
+ ", mSubscribeType=" + mSubscribeType + ", mTtlSec=" + mTtlSec
- + ", mEnableTerminateNotification=" + mEnableTerminateNotification + "]";
+ + ", mEnableTerminateNotification=" + mEnableTerminateNotification
+ + ", mMinDistanceMm=" + mMinDistanceMm
+ + ", mMinDistanceMmSet=" + mMinDistanceMmSet
+ + ", mMaxDistanceMm=" + mMaxDistanceMm
+ + ", mMaxDistanceMmSet=" + mMaxDistanceMmSet + "]";
}
@Override
@@ -118,6 +140,10 @@ public final class SubscribeConfig implements Parcelable {
dest.writeInt(mSubscribeType);
dest.writeInt(mTtlSec);
dest.writeInt(mEnableTerminateNotification ? 1 : 0);
+ dest.writeInt(mMinDistanceMm);
+ dest.writeInt(mMinDistanceMmSet ? 1 : 0);
+ dest.writeInt(mMaxDistanceMm);
+ dest.writeInt(mMaxDistanceMmSet ? 1 : 0);
}
public static final Creator<SubscribeConfig> CREATOR = new Creator<SubscribeConfig>() {
@@ -134,9 +160,14 @@ public final class SubscribeConfig implements Parcelable {
int subscribeType = in.readInt();
int ttlSec = in.readInt();
boolean enableTerminateNotification = in.readInt() != 0;
-
- return new SubscribeConfig(serviceName, ssi, matchFilter, subscribeType,
- ttlSec, enableTerminateNotification);
+ int minDistanceMm = in.readInt();
+ boolean minDistanceMmSet = in.readInt() != 0;
+ int maxDistanceMm = in.readInt();
+ boolean maxDistanceMmSet = in.readInt() != 0;
+
+ return new SubscribeConfig(serviceName, ssi, matchFilter, subscribeType, ttlSec,
+ enableTerminateNotification, minDistanceMmSet, minDistanceMm, maxDistanceMmSet,
+ maxDistanceMm);
}
};
@@ -152,23 +183,37 @@ public final class SubscribeConfig implements Parcelable {
SubscribeConfig lhs = (SubscribeConfig) o;
- return Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(mServiceSpecificInfo,
- lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, lhs.mMatchFilter)
- && mSubscribeType == lhs.mSubscribeType
- && mTtlSec == lhs.mTtlSec
- && mEnableTerminateNotification == lhs.mEnableTerminateNotification;
+ if (!(Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(
+ mServiceSpecificInfo, lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter,
+ lhs.mMatchFilter) && mSubscribeType == lhs.mSubscribeType && mTtlSec == lhs.mTtlSec
+ && mEnableTerminateNotification == lhs.mEnableTerminateNotification
+ && mMinDistanceMmSet == lhs.mMinDistanceMmSet
+ && mMaxDistanceMmSet == lhs.mMaxDistanceMmSet)) {
+ return false;
+ }
+
+ if (mMinDistanceMmSet && mMinDistanceMm != lhs.mMinDistanceMm) {
+ return false;
+ }
+
+ if (mMaxDistanceMmSet && mMaxDistanceMm != lhs.mMaxDistanceMm) {
+ return false;
+ }
+
+ return true;
}
@Override
public int hashCode() {
- int result = 17;
+ int result = Objects.hash(mServiceName, mServiceSpecificInfo, mMatchFilter, mSubscribeType,
+ mTtlSec, mEnableTerminateNotification, mMinDistanceMmSet, mMaxDistanceMmSet);
- result = 31 * result + Arrays.hashCode(mServiceName);
- result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
- result = 31 * result + Arrays.hashCode(mMatchFilter);
- result = 31 * result + mSubscribeType;
- result = 31 * result + mTtlSec;
- result = 31 * result + (mEnableTerminateNotification ? 1 : 0);
+ if (mMinDistanceMmSet) {
+ result = Objects.hash(result, mMinDistanceMm);
+ }
+ if (mMaxDistanceMmSet) {
+ result = Objects.hash(result, mMaxDistanceMm);
+ }
return result;
}
@@ -213,6 +258,17 @@ public final class SubscribeConfig implements Parcelable {
"Match filter longer than supported by device characteristics");
}
}
+
+ if (mMinDistanceMmSet && mMinDistanceMm < 0) {
+ throw new IllegalArgumentException("Minimum distance must be non-negative");
+ }
+ if (mMaxDistanceMmSet && mMaxDistanceMm < 0) {
+ throw new IllegalArgumentException("Maximum distance must be non-negative");
+ }
+ if (mMinDistanceMmSet && mMaxDistanceMmSet && mMaxDistanceMm <= mMinDistanceMm) {
+ throw new IllegalArgumentException(
+ "Maximum distance must be greater than minimum distance");
+ }
}
/**
@@ -225,6 +281,10 @@ public final class SubscribeConfig implements Parcelable {
private int mSubscribeType = SUBSCRIBE_TYPE_PASSIVE;
private int mTtlSec = 0;
private boolean mEnableTerminateNotification = true;
+ private boolean mMinDistanceMmSet = false;
+ private int mMinDistanceMm;
+ private boolean mMaxDistanceMmSet = false;
+ private int mMaxDistanceMm;
/**
* Specify the service name of the subscribe session. The actual on-air
@@ -350,13 +410,69 @@ public final class SubscribeConfig implements Parcelable {
}
/**
+ * Configure the minimum distance to a discovered publisher at which to trigger a discovery
+ * notification. I.e. discovery will only be triggered if we've found a matching publisher
+ * (based on the other criteria in this configuration) <b>and</b> the distance to the
+ * publisher is > the value specified in this API.
+ * <p>
+ * Can be used in conjunction with {@link #setMaxDistanceMm(int)} to specify a geo-fence,
+ * i.e. discovery with min < distance < max.
+ * <p>
+ * If this API is called, the subscriber requires ranging. In such a case, the publisher
+ * peer must enable ranging using
+ * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. Otherwise discovery will
+ * never be triggered.
+ *
+ * @param minDistanceMm Minimum distance, in mm, to the publisher above which to trigger
+ * discovery.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ *
+ * @hide
+ */
+ public Builder setMinDistanceMm(int minDistanceMm) {
+ mMinDistanceMm = minDistanceMm;
+ mMinDistanceMmSet = true;
+ return this;
+ }
+
+ /**
+ * Configure the maximum distance to a discovered publisher at which to trigger a discovery
+ * notification. I.e. discovery will only be triggered if we've found a matching publisher
+ * (based on the other criteria in this configuration) <b>and</b> the distance to the
+ * publisher is < the value specified in this API.
+ * <p>
+ * Can be used in conjunction with {@link #setMinDistanceMm(int)} to specify a geo-fence,
+ * i.e. discovery with min < distance < max.
+ * <p>
+ * If this API is called, the subscriber requires ranging. In such a case, the publisher
+ * peer must enable ranging using
+ * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. Otherwise discovery will
+ * never be triggered.
+ *
+ * @param maxDistanceMm Maximum distance, in mm, to the publisher below which to trigger
+ * discovery.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ *
+ * @hide
+ */
+ public Builder setMaxDistanceMm(int maxDistanceMm) {
+ mMaxDistanceMm = maxDistanceMm;
+ mMaxDistanceMmSet = true;
+ return this;
+ }
+
+ /**
* Build {@link SubscribeConfig} given the current requests made on the
* builder.
*/
public SubscribeConfig build() {
return new SubscribeConfig(mServiceName, mServiceSpecificInfo, mMatchFilter,
- mSubscribeType, mTtlSec,
- mEnableTerminateNotification);
+ mSubscribeType, mTtlSec, mEnableTerminateNotification,
+ mMinDistanceMmSet, mMinDistanceMm, mMaxDistanceMmSet, mMaxDistanceMm);
}
}
}
diff --git a/android/net/wifi/aware/WifiAwareManager.java b/android/net/wifi/aware/WifiAwareManager.java
index ed6804d5..166da48e 100644
--- a/android/net/wifi/aware/WifiAwareManager.java
+++ b/android/net/wifi/aware/WifiAwareManager.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.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkRequest;
@@ -564,6 +564,7 @@ public class WifiAwareManager {
private static final int CALLBACK_MESSAGE_SEND_SUCCESS = 5;
private static final int CALLBACK_MESSAGE_SEND_FAIL = 6;
private static final int CALLBACK_MESSAGE_RECEIVED = 7;
+ private static final int CALLBACK_MATCH_WITH_DISTANCE = 8;
private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
private static final String MESSAGE_BUNDLE_KEY_MESSAGE2 = "message2";
@@ -618,7 +619,9 @@ public class WifiAwareManager {
case CALLBACK_SESSION_TERMINATED:
onProxySessionTerminated(msg.arg1);
break;
- case CALLBACK_MATCH: {
+ case CALLBACK_MATCH:
+ case CALLBACK_MATCH_WITH_DISTANCE:
+ {
List<byte[]> matchFilter = null;
byte[] arg = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2);
try {
@@ -629,9 +632,16 @@ public class WifiAwareManager {
+ new String(HexEncoding.encode(arg))
+ "' - cannot be parsed: e=" + e);
}
- mOriginalCallback.onServiceDiscovered(new PeerHandle(msg.arg1),
- msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
- matchFilter);
+ if (msg.what == CALLBACK_MATCH) {
+ mOriginalCallback.onServiceDiscovered(new PeerHandle(msg.arg1),
+ msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
+ matchFilter);
+ } else {
+ mOriginalCallback.onServiceDiscoveredWithinRange(
+ new PeerHandle(msg.arg1),
+ msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
+ matchFilter, msg.arg2);
+ }
break;
}
case CALLBACK_MESSAGE_SEND_SUCCESS:
@@ -684,21 +694,38 @@ public class WifiAwareManager {
mHandler.sendMessage(msg);
}
- @Override
- public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) {
- if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
-
+ private void onMatchCommon(int messageType, int peerId, byte[] serviceSpecificInfo,
+ byte[] matchFilter, int distanceMm) {
Bundle data = new Bundle();
data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, serviceSpecificInfo);
data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2, matchFilter);
- Message msg = mHandler.obtainMessage(CALLBACK_MATCH);
+ Message msg = mHandler.obtainMessage(messageType);
msg.arg1 = peerId;
+ msg.arg2 = distanceMm;
msg.setData(data);
mHandler.sendMessage(msg);
}
@Override
+ public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) {
+ if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
+
+ onMatchCommon(CALLBACK_MATCH, peerId, serviceSpecificInfo, matchFilter, 0);
+ }
+
+ @Override
+ public void onMatchWithDistance(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter,
+ int distanceMm) {
+ if (VDBG) {
+ Log.v(TAG, "onMatchWithDistance: peerId=" + peerId + ", distanceMm=" + distanceMm);
+ }
+
+ onMatchCommon(CALLBACK_MATCH_WITH_DISTANCE, peerId, serviceSpecificInfo, matchFilter,
+ distanceMm);
+ }
+
+ @Override
public void onMessageSendSuccess(int messageId) {
if (VDBG) Log.v(TAG, "onMessageSendSuccess");
diff --git a/android/net/wifi/hotspot2/ProvisioningCallback.java b/android/net/wifi/hotspot2/ProvisioningCallback.java
new file mode 100644
index 00000000..8b86cdde
--- /dev/null
+++ b/android/net/wifi/hotspot2/ProvisioningCallback.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2;
+
+import android.os.Handler;
+
+/**
+ * Base class for provisioning callbacks. Should be extended by applications and set when calling
+ * {@link WifiManager#startSubscriptionProvisiong(OsuProvider, ProvisioningCallback, Handler)}.
+ *
+ * @hide
+ */
+public abstract class ProvisioningCallback {
+
+ /**
+ * The reason code for Provisioning Failure due to connection failure to OSU AP.
+ * @hide
+ */
+ public static final int OSU_FAILURE_AP_CONNECTION = 1;
+
+ /**
+ * The status code for Provisioning flow to indicate connecting to OSU AP
+ * @hide
+ */
+ public static final int OSU_STATUS_AP_CONNECTING = 1;
+
+ /**
+ * The status code for Provisioning flow to indicate connected to OSU AP
+ * @hide
+ */
+ public static final int OSU_STATUS_AP_CONNECTED = 2;
+
+ /**
+ * Provisioning status for OSU failure
+ * @param status indicates error condition
+ */
+ public abstract void onProvisioningFailure(int status);
+
+ /**
+ * Provisioning status when OSU is in progress
+ * @param status indicates status of OSU flow
+ */
+ public abstract void onProvisioningStatus(int status);
+}
+
diff --git a/android/net/wifi/rtt/WifiRttManager.java b/android/net/wifi/rtt/WifiRttManager.java
index 128d6c91..735e872e 100644
--- a/android/net/wifi/rtt/WifiRttManager.java
+++ b/android/net/wifi/rtt/WifiRttManager.java
@@ -44,7 +44,7 @@ import java.util.List;
@SystemService(Context.WIFI_RTT2_SERVICE)
public class WifiRttManager {
private static final String TAG = "WifiRttManager";
- private static final boolean VDBG = true;
+ private static final boolean VDBG = false;
private final Context mContext;
private final IWifiRttManager mService;
diff --git a/android/os/BatteryManager.java b/android/os/BatteryManager.java
index 6e0f70c1..843bdb50 100644
--- a/android/os/BatteryManager.java
+++ b/android/os/BatteryManager.java
@@ -18,6 +18,7 @@ package android.os;
import android.annotation.SystemService;
import android.content.Context;
+import android.content.Intent;
import android.hardware.health.V1_0.Constants;
import com.android.internal.app.IBatteryStats;
@@ -56,6 +57,14 @@ public class BatteryManager {
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+ * Boolean field indicating whether the battery is currently considered to be
+ * low, that is whether a {@link Intent#ACTION_BATTERY_LOW} broadcast
+ * has been sent.
+ */
+ public static final String EXTRA_BATTERY_LOW = "battery_low";
+
+ /**
+ * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the maximum battery level.
*/
public static final String EXTRA_SCALE = "scale";
diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java
index a8bd9403..811091e3 100644
--- a/android/os/BatteryStats.java
+++ b/android/os/BatteryStats.java
@@ -222,8 +222,11 @@ public abstract class BatteryStats implements Parcelable {
* - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly]
* New in version 27:
* - Always On Display (screen doze mode) time and power
+ * New in version 28:
+ * - Light/Deep Doze power
+ * - WiFi Multicast Wakelock statistics (count & duration)
*/
- static final int CHECKIN_VERSION = 27;
+ static final int CHECKIN_VERSION = 28;
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -311,6 +314,8 @@ public abstract class BatteryStats implements Parcelable {
private static final String CAMERA_DATA = "cam";
private static final String VIDEO_DATA = "vid";
private static final String AUDIO_DATA = "aud";
+ private static final String WIFI_MULTICAST_TOTAL_DATA = "wmct";
+ private static final String WIFI_MULTICAST_DATA = "wmc";
public static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity";
@@ -514,6 +519,13 @@ public abstract class BatteryStats implements Parcelable {
public abstract ArrayMap<String, ? extends Wakelock> getWakelockStats();
/**
+ * Returns the WiFi Multicast Wakelock statistics.
+ *
+ * @return a Timer Object for the per uid Multicast statistics.
+ */
+ public abstract Timer getMulticastWakelockStats();
+
+ /**
* Returns a mapping containing sync statistics.
*
* @return a Map from Strings to Timer objects.
@@ -2696,6 +2708,18 @@ public abstract class BatteryStats implements Parcelable {
public abstract long getUahDischarge(int which);
/**
+ * @return the amount of battery discharge while the device is in light idle mode, measured in
+ * micro-Ampere-hours.
+ */
+ public abstract long getUahDischargeLightDoze(int which);
+
+ /**
+ * @return the amount of battery discharge while the device is in deep idle mode, measured in
+ * micro-Ampere-hours.
+ */
+ public abstract long getUahDischargeDeepDoze(int which);
+
+ /**
* Returns the estimated real battery capacity, which may be less than the capacity
* declared by the PowerProfile.
* @return The estimated battery capacity in mAh.
@@ -3327,6 +3351,8 @@ public abstract class BatteryStats implements Parcelable {
final long dischargeCount = getUahDischarge(which);
final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
+ final long dischargeLightDozeCount = getUahDischargeLightDoze(which);
+ final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which);
final StringBuilder sb = new StringBuilder(128);
@@ -3347,13 +3373,16 @@ public abstract class BatteryStats implements Parcelable {
screenDozeTime / 1000);
- // Calculate wakelock times across all uids.
+ // Calculate both wakelock and wifi multicast wakelock times across all uids.
long fullWakeLockTimeTotal = 0;
long partialWakeLockTimeTotal = 0;
+ long multicastWakeLockTimeTotalMicros = 0;
+ int multicastWakeLockCountTotal = 0;
for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
+ // First calculating the wakelock stats
final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
= u.getWakelockStats();
for (int iw=wakelocks.size()-1; iw>=0; iw--) {
@@ -3371,6 +3400,13 @@ public abstract class BatteryStats implements Parcelable {
rawRealtime, which);
}
}
+
+ // Now calculating the wifi multicast wakelock stats
+ final Timer mcTimer = u.getMulticastWakelockStats();
+ if (mcTimer != null) {
+ multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which);
+ multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
+ }
}
// Dump network stats
@@ -3486,6 +3522,11 @@ public abstract class BatteryStats implements Parcelable {
}
dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args);
+ // Dump Multicast total stats
+ dumpLine(pw, 0 /* uid */, category, WIFI_MULTICAST_TOTAL_DATA,
+ multicastWakeLockTimeTotalMicros / 1000,
+ multicastWakeLockCountTotal);
+
if (which == STATS_SINCE_UNPLUGGED) {
dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(),
getDischargeCurrentLevel());
@@ -3497,14 +3538,16 @@ public abstract class BatteryStats implements Parcelable {
getDischargeStartLevel()-getDischargeCurrentLevel(),
getDischargeAmountScreenOn(), getDischargeAmountScreenOff(),
dischargeCount / 1000, dischargeScreenOffCount / 1000,
- getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000);
+ getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000,
+ dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000);
} else {
dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA,
getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(),
getDischargeAmountScreenOnSinceCharge(),
getDischargeAmountScreenOffSinceCharge(),
dischargeCount / 1000, dischargeScreenOffCount / 1000,
- getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000);
+ getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000,
+ dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000);
}
if (reqUid < 0) {
@@ -3810,6 +3853,18 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ // WiFi Multicast Wakelock Statistics
+ final Timer mcTimer = u.getMulticastWakelockStats();
+ if (mcTimer != null) {
+ final long totalMcWakelockTimeMs =
+ mcTimer.getTotalTimeLocked(rawRealtime, which) / 1000 ;
+ final int countMcWakelock = mcTimer.getCountLocked(which);
+ if(totalMcWakelockTimeMs > 0) {
+ dumpLine(pw, uid, category, WIFI_MULTICAST_DATA,
+ totalMcWakelockTimeMs, countMcWakelock);
+ }
+ }
+
final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats();
for (int isy=syncs.size()-1; isy>=0; isy--) {
final Timer timer = syncs.valueAt(isy);
@@ -4169,6 +4224,26 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
+ final long dischargeLightDozeCount = getUahDischargeLightDoze(which);
+ if (dischargeLightDozeCount >= 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Device light doze discharge: ");
+ sb.append(BatteryStatsHelper.makemAh(dischargeLightDozeCount / 1000.0));
+ sb.append(" mAh");
+ pw.println(sb.toString());
+ }
+
+ final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which);
+ if (dischargeDeepDozeCount >= 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Device deep doze discharge: ");
+ sb.append(BatteryStatsHelper.makemAh(dischargeDeepDozeCount / 1000.0));
+ sb.append(" mAh");
+ pw.println(sb.toString());
+ }
+
pw.print(" Start clock time: ");
pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", getStartClockTime()).toString());
@@ -4289,15 +4364,18 @@ public abstract class BatteryStats implements Parcelable {
pw.print(" Connectivity changes: "); pw.println(connChanges);
}
- // Calculate wakelock times across all uids.
+ // Calculate both wakelock and wifi multicast wakelock times across all uids.
long fullWakeLockTimeTotalMicros = 0;
long partialWakeLockTimeTotalMicros = 0;
+ long multicastWakeLockTimeTotalMicros = 0;
+ int multicastWakeLockCountTotal = 0;
final ArrayList<TimerEntry> timers = new ArrayList<>();
for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
+ // First calculate wakelock statistics
final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
= u.getWakelockStats();
for (int iw=wakelocks.size()-1; iw>=0; iw--) {
@@ -4325,6 +4403,13 @@ public abstract class BatteryStats implements Parcelable {
}
}
}
+
+ // Next calculate wifi multicast wakelock statistics
+ final Timer mcTimer = u.getMulticastWakelockStats();
+ if (mcTimer != null) {
+ multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which);
+ multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
+ }
}
final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
@@ -4354,6 +4439,20 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
+ if (multicastWakeLockTimeTotalMicros != 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Total WiFi Multicast wakelock Count: ");
+ sb.append(multicastWakeLockCountTotal);
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Total WiFi Multicast wakelock time: ");
+ formatTimeMsNoSpace(sb, (multicastWakeLockTimeTotalMicros + 500) / 1000);
+ pw.println(sb.toString());
+ }
+
pw.println("");
pw.print(prefix);
sb.setLength(0);
@@ -5271,6 +5370,24 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ // Calculate multicast wakelock stats
+ final Timer mcTimer = u.getMulticastWakelockStats();
+ if (mcTimer != null) {
+ final long multicastWakeLockTimeMicros = mcTimer.getTotalTimeLocked(rawRealtime, which);
+ final int multicastWakeLockCount = mcTimer.getCountLocked(which);
+
+ if (multicastWakeLockTimeMicros > 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" WiFi Multicast Wakelock");
+ sb.append(" count = ");
+ sb.append(multicastWakeLockCount);
+ sb.append(" time = ");
+ formatTimeMsNoSpace(sb, (multicastWakeLockTimeMicros + 500) / 1000);
+ pw.println(sb.toString());
+ }
+ }
+
final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats();
for (int isy=syncs.size()-1; isy>=0; isy--) {
final Timer timer = syncs.valueAt(isy);
@@ -7047,6 +7164,10 @@ public abstract class BatteryStats implements Parcelable {
proto.end(wToken);
}
+ // Wifi Multicast Wakelock (WIFI_MULTICAST_WAKELOCK_DATA)
+ dumpTimer(proto, UidProto.WIFI_MULTICAST_WAKELOCK, u.getMulticastWakelockStats(),
+ rawRealtimeUs, which);
+
// Wakeup alarms (WAKEUP_ALARM_DATA)
for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
final Uid.Pkg ps = packageStats.valueAt(ipkg);
@@ -7131,6 +7252,10 @@ public abstract class BatteryStats implements Parcelable {
getUahDischargeScreenOff(which) / 1000);
proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE,
getUahDischargeScreenDoze(which) / 1000);
+ proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_LIGHT_DOZE,
+ getUahDischargeLightDoze(which) / 1000);
+ proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_DEEP_DOZE,
+ getUahDischargeDeepDoze(which) / 1000);
proto.end(bdToken);
// Time remaining
@@ -7299,6 +7424,30 @@ public abstract class BatteryStats implements Parcelable {
getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT));
proto.end(mToken);
+ // Wifi multicast wakelock total stats (WIFI_MULTICAST_WAKELOCK_TOTAL_DATA)
+ // Calculate multicast wakelock stats across all uids.
+ long multicastWakeLockTimeTotalUs = 0;
+ int multicastWakeLockCountTotal = 0;
+
+ for (int iu = 0; iu < uidStats.size(); iu++) {
+ final Uid u = uidStats.valueAt(iu);
+
+ final Timer mcTimer = u.getMulticastWakelockStats();
+
+ if (mcTimer != null) {
+ multicastWakeLockTimeTotalUs +=
+ mcTimer.getTotalTimeLocked(rawRealtimeUs, which);
+ multicastWakeLockCountTotal += mcTimer.getCountLocked(which);
+ }
+ }
+
+ final long wmctToken = proto.start(SystemProto.WIFI_MULTICAST_WAKELOCK_TOTAL);
+ proto.write(SystemProto.WifiMulticastWakelockTotal.DURATION_MS,
+ multicastWakeLockTimeTotalUs / 1000);
+ proto.write(SystemProto.WifiMulticastWakelockTotal.COUNT,
+ multicastWakeLockCountTotal);
+ proto.end(wmctToken);
+
// Power use item (POWER_USE_ITEM_DATA)
final List<BatterySipper> sippers = helper.getUsageList();
if (sippers != null) {
diff --git a/android/os/Build.java b/android/os/Build.java
index 935f5f3b..02c7bd64 100644
--- a/android/os/Build.java
+++ b/android/os/Build.java
@@ -19,9 +19,11 @@ package android.os;
import android.Manifest;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.app.Application;
import android.content.Context;
import android.text.TextUtils;
import android.util.Slog;
+import android.view.View;
import com.android.internal.telephony.TelephonyProperties;
@@ -103,7 +105,7 @@ public class Build {
/**
* A hardware serial number, if available. Alphanumeric only, case-insensitive.
- * For apps targeting SDK higher than {@link Build.VERSION_CODES#N_MR1} this
+ * For apps targeting SDK higher than {@link Build.VERSION_CODES#O_MR1} this
* field is set to {@link Build#UNKNOWN}.
*
* @deprecated Use {@link #getSerial()} instead.
@@ -761,6 +763,80 @@ public class Build {
* <p>Applications targeting this or a later release will get these
* new changes in behavior:</p>
* <ul>
+ * <li><a href="{@docRoot}about/versions/oreo/background.html">Background execution limits</a>
+ * are applied to the application.</li>
+ * <li>The behavior of AccountManager's
+ * {@link android.accounts.AccountManager#getAccountsByType},
+ * {@link android.accounts.AccountManager#getAccountsByTypeAndFeatures}, and
+ * {@link android.accounts.AccountManager#hasFeatures} has changed as documented there.</li>
+ * <li>{@link android.app.ActivityManager.RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE_PRE_26}
+ * is now returned as
+ * {@link android.app.ActivityManager.RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}.</li>
+ * <li>The {@link android.app.NotificationManager} now requires the use of notification
+ * channels.</li>
+ * <li>Changes to the strict mode that are set in
+ * {@link Application#onCreate Application.onCreate} will no longer be clobbered after
+ * that function returns.</li>
+ * <li>A shared library apk with native code will have that native code included in
+ * the library path of its clients.</li>
+ * <li>{@link android.content.Context#getSharedPreferences Context.getSharedPreferences}
+ * in credential encrypted storage will throw an exception before the user is unlocked.</li>
+ * <li>Attempting to retrieve a {@link Context#FINGERPRINT_SERVICE} on a device that
+ * does not support that feature will now throw a runtime exception.</li>
+ * <li>{@link android.app.Fragment} will stop any active view animations when
+ * the fragment is stopped.</li>
+ * <li>Some compatibility code in Resources that attempts to use the default Theme
+ * the app may be using will be turned off, requiring the app to explicitly request
+ * resources with the right theme.</li>
+ * <li>{@link android.content.ContentResolver#notifyChange ContentResolver.notifyChange} and
+ * {@link android.content.ContentResolver#registerContentObserver
+ * ContentResolver.registerContentObserver}
+ * will throw a SecurityException if the caller does not have permission to access
+ * the provider (or the provider doesn't exit); otherwise the call will be silently
+ * ignored.</li>
+ * <li>{@link android.hardware.camera2.CameraDevice#createCaptureRequest
+ * CameraDevice.createCaptureRequest} will enable
+ * {@link android.hardware.camera2.CaptureRequest#CONTROL_ENABLE_ZSL} by default for
+ * still image capture.</li>
+ * <li>WallpaperManager's {@link android.app.WallpaperManager#getWallpaperFile},
+ * {@link android.app.WallpaperManager#getDrawable},
+ * {@link android.app.WallpaperManager#getFastDrawable},
+ * {@link android.app.WallpaperManager#peekDrawable}, and
+ * {@link android.app.WallpaperManager#peekFastDrawable} will throw an exception
+ * if you can not access the wallpaper.</li>
+ * <li>The behavior of
+ * {@link android.hardware.usb.UsbDeviceConnection#requestWait UsbDeviceConnection.requestWait}
+ * is modified as per the documentation there.</li>
+ * <li>{@link StrictMode.VmPolicy.Builder#detectAll StrictMode.VmPolicy.Builder.detectAll}
+ * will also enable {@link StrictMode.VmPolicy.Builder#detectContentUriWithoutPermission}
+ * and {@link StrictMode.VmPolicy.Builder#detectUntaggedSockets}.</li>
+ * <li>{@link StrictMode.ThreadPolicy.Builder#detectAll StrictMode.ThreadPolicy.Builder.detectAll}
+ * will also enable {@link StrictMode.ThreadPolicy.Builder#detectUnbufferedIo}.</li>
+ * <li>{@link android.provider.DocumentsContract}'s various methods will throw failure
+ * exceptions back to the caller instead of returning null.
+ * <li>{@link View#hasFocusable View.hasFocusable} now includes auto-focusable views.</li>
+ * <li>{@link android.view.SurfaceView} will no longer always change the underlying
+ * Surface object when something about it changes; apps need to look at the current
+ * state of the object to determine which things they are interested in have changed.</li>
+ * <li>{@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} must be
+ * used for overlay windows, other system overlay window types are not allowed.</li>
+ * <li>{@link android.view.ViewTreeObserver#addOnDrawListener
+ * ViewTreeObserver.addOnDrawListener} will throw an exception if called from within
+ * onDraw.</li>
+ * <li>{@link android.graphics.Canvas#setBitmap Canvas.setBitmap} will no longer preserve
+ * the current matrix and clip stack of the canvas.</li>
+ * <li>{@link android.widget.ListPopupWindow#setHeight ListPopupWindow.setHeight}
+ * will throw an exception if a negative height is supplied.</li>
+ * <li>{@link android.widget.TextView} will use internationalized input for numbers,
+ * dates, and times.</li>
+ * <li>{@link android.widget.Toast} must be used for showing toast windows; the toast
+ * window type can not be directly used.</li>
+ * <li>{@link android.net.wifi.WifiManager#getConnectionInfo WifiManager.getConnectionInfo}
+ * requires that the caller hold the location permission to return BSSID/SSID</li>
+ * <li>{@link android.net.wifi.p2p.WifiP2pManager#requestPeers WifiP2pManager.requestPeers}
+ * requires the caller hold the location permission.</li>
+ * <li>{@link android.R.attr#maxAspectRatio} defaults to 0, meaning there is no restriction
+ * on the app's maximum aspect ratio (so it can be stretched to fill larger screens).</li>
* <li>{@link android.R.attr#focusable} defaults to a new state ({@code auto}) where it will
* inherit the value of {@link android.R.attr#clickable} unless explicitly overridden.</li>
* <li>A default theme-appropriate focus-state highlight will be supplied to all Views
@@ -772,6 +848,15 @@ public class Build {
/**
* O MR1.
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li>Apps exporting and linking to apk shared libraries must explicitly
+ * enumerate all signing certificates in a consistent order.</li>
+ * <li>{@link android.R.attr#screenOrientation} can not be used to request a fixed
+ * orientation if the associated activity is not fullscreen and opaque.</li>
+ * </ul>
*/
public static final int O_MR1 = 27;
diff --git a/android/os/GraphicsEnvironment.java b/android/os/GraphicsEnvironment.java
index 07c60551..f2e0bddb 100644
--- a/android/os/GraphicsEnvironment.java
+++ b/android/os/GraphicsEnvironment.java
@@ -22,6 +22,7 @@ import android.content.pm.PackageManager;
import android.opengl.EGL14;
import android.os.Build;
import android.os.SystemProperties;
+import android.provider.Settings;
import android.util.Log;
import dalvik.system.VMRuntime;
@@ -29,18 +30,110 @@ import dalvik.system.VMRuntime;
import java.io.File;
/** @hide */
-public final class GraphicsEnvironment {
+public class GraphicsEnvironment {
+
+ private static final GraphicsEnvironment sInstance = new GraphicsEnvironment();
+
+ /**
+ * Returns the shared {@link GraphicsEnvironment} instance.
+ */
+ public static GraphicsEnvironment getInstance() {
+ return sInstance;
+ }
private static final boolean DEBUG = false;
private static final String TAG = "GraphicsEnvironment";
private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
+ private ClassLoader mClassLoader;
+ private String mLayerPath;
+ private String mDebugLayerPath;
+
+ /**
+ * Set up GraphicsEnvironment
+ */
+ public void setup(Context context) {
+ setupGpuLayers(context);
+ chooseDriver(context);
+ }
+
+ /**
+ * Check whether application is debuggable
+ */
+ private static boolean isDebuggable(Context context) {
+ return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) > 0;
+ }
+
+ /**
+ * Store the layer paths available to the loader.
+ */
+ public void setLayerPaths(ClassLoader classLoader,
+ String layerPath,
+ String debugLayerPath) {
+ // We have to store these in the class because they are set up before we
+ // have access to the Context to properly set up GraphicsEnvironment
+ mClassLoader = classLoader;
+ mLayerPath = layerPath;
+ mDebugLayerPath = debugLayerPath;
+ }
+
+ /**
+ * Set up layer search paths for all apps
+ * If debuggable, check for additional debug settings
+ */
+ private void setupGpuLayers(Context context) {
+
+ String layerPaths = "";
+
+ // Only enable additional debug functionality if the following conditions are met:
+ // 1. App is debuggable
+ // 2. ENABLE_GPU_DEBUG_LAYERS is true
+ // 3. Package name is equal to GPU_DEBUG_APP
+
+ if (isDebuggable(context)) {
+
+ int enable = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0);
+
+ if (enable != 0) {
+
+ String gpuDebugApp = Settings.Global.getString(context.getContentResolver(),
+ Settings.Global.GPU_DEBUG_APP);
+
+ String packageName = context.getPackageName();
+
+ if ((gpuDebugApp != null && packageName != null)
+ && (!gpuDebugApp.isEmpty() && !packageName.isEmpty())
+ && gpuDebugApp.equals(packageName)) {
+ Log.i(TAG, "GPU debug layers enabled for " + packageName);
+
+ // Prepend the debug layer path as a searchable path.
+ // This will ensure debug layers added will take precedence over
+ // the layers specified by the app.
+ layerPaths = mDebugLayerPath + ":";
+
+ String layers = Settings.Global.getString(context.getContentResolver(),
+ Settings.Global.GPU_DEBUG_LAYERS);
+
+ Log.i(TAG, "Debug layer list: " + layers);
+ if (layers != null && !layers.isEmpty()) {
+ setDebugLayers(layers);
+ }
+ }
+ }
+
+ }
+
+ // Include the app's lib directory in all cases
+ layerPaths += mLayerPath;
+
+ setLayerPaths(mClassLoader, layerPaths);
+ }
+
/**
* Choose whether the current process should use the builtin or an updated driver.
- *
- * @hide
*/
- public static void chooseDriver(Context context) {
+ private static void chooseDriver(Context context) {
String driverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER);
if (driverPackageName == null || driverPackageName.isEmpty()) {
return;
@@ -99,8 +192,6 @@ public final class GraphicsEnvironment {
* on a separate thread, it can usually be finished well before the UI is ready to be drawn.
*
* Should only be called after chooseDriver().
- *
- * @hide
*/
public static void earlyInitEGL() {
Thread eglInitThread = new Thread(
@@ -124,6 +215,7 @@ public final class GraphicsEnvironment {
return null;
}
+ private static native void setLayerPaths(ClassLoader classLoader, String layerPaths);
+ private static native void setDebugLayers(String layers);
private static native void setDriverPath(String path);
-
}
diff --git a/android/os/HidlSupport.java b/android/os/HidlSupport.java
index 3544ea1e..a080c8dc 100644
--- a/android/os/HidlSupport.java
+++ b/android/os/HidlSupport.java
@@ -179,4 +179,9 @@ public class HidlSupport {
}
return Objects.equals(lft.asBinder(), ((IHwInterface) rgt).asBinder());
}
+
+ /**
+ * Return PID of process if sharable to clients.
+ */
+ public static native int getPidIfSharable();
}
diff --git a/android/os/Parcel.java b/android/os/Parcel.java
index 10adb5a6..62bb3854 100644
--- a/android/os/Parcel.java
+++ b/android/os/Parcel.java
@@ -20,6 +20,7 @@ import android.annotation.Nullable;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Size;
import android.util.SizeF;
@@ -191,6 +192,7 @@ import java.util.Set;
* {@link #readSparseArray(ClassLoader)}.
*/
public final class Parcel {
+
private static final boolean DEBUG_RECYCLE = false;
private static final boolean DEBUG_ARRAY_MAP = false;
private static final String TAG = "Parcel";
@@ -209,6 +211,12 @@ public final class Parcel {
private RuntimeException mStack;
+ /**
+ * Whether or not to parcel the stack trace of an exception. This has a performance
+ * impact, so should only be included in specific processes and only on debug builds.
+ */
+ private static boolean sParcelExceptionStackTrace;
+
private static final int POOL_SIZE = 6;
private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];
private static final Parcel[] sHolderPool = new Parcel[POOL_SIZE];
@@ -325,6 +333,11 @@ public final class Parcel {
private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
private static native void nativeEnforceInterface(long nativePtr, String interfaceName);
+ /** Last time exception with a stack trace was written */
+ private static volatile long sLastWriteExceptionStackTrace;
+ /** Used for throttling of writing stack trace, which is costly */
+ private static final int WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS = 1000;
+
@CriticalNative
private static native long nativeGetBlobAshmemSize(long nativePtr);
@@ -1696,6 +1709,11 @@ public final class Parcel {
}
}
+ /** @hide For debugging purposes */
+ public static void setStackTraceParceling(boolean enabled) {
+ sParcelExceptionStackTrace = enabled;
+ }
+
/**
* Special function for writing an exception result at the header of
* a parcel, to be used when returning an exception from a transaction.
@@ -1753,6 +1771,27 @@ public final class Parcel {
throw new RuntimeException(e);
}
writeString(e.getMessage());
+ final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
+ if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
+ > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
+ sLastWriteExceptionStackTrace = timeNow;
+ final int sizePosition = dataPosition();
+ writeInt(0); // Header size will be filled in later
+ StackTraceElement[] stackTrace = e.getStackTrace();
+ final int truncatedSize = Math.min(stackTrace.length, 5);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < truncatedSize; i++) {
+ sb.append("\tat ").append(stackTrace[i]).append('\n');
+ }
+ writeString(sb.toString());
+ final int payloadPosition = dataPosition();
+ setDataPosition(sizePosition);
+ // Write stack trace header size. Used in native side to skip the header
+ writeInt(payloadPosition - sizePosition);
+ setDataPosition(payloadPosition);
+ } else {
+ writeInt(0);
+ }
switch (code) {
case EX_SERVICE_SPECIFIC:
writeInt(((ServiceSpecificException) e).errorCode);
@@ -1818,7 +1857,26 @@ public final class Parcel {
int code = readExceptionCode();
if (code != 0) {
String msg = readString();
- readException(code, msg);
+ String remoteStackTrace = null;
+ final int remoteStackPayloadSize = readInt();
+ if (remoteStackPayloadSize > 0) {
+ remoteStackTrace = readString();
+ }
+ Exception e = createException(code, msg);
+ // Attach remote stack trace if availalble
+ if (remoteStackTrace != null) {
+ RemoteException cause = new RemoteException(
+ "Remote stack trace:\n" + remoteStackTrace, null, false, false);
+ try {
+ Throwable rootCause = ExceptionUtils.getRootCause(e);
+ if (rootCause != null) {
+ rootCause.initCause(cause);
+ }
+ } catch (RuntimeException ex) {
+ Log.e(TAG, "Cannot set cause " + cause + " for " + e, ex);
+ }
+ }
+ SneakyThrow.sneakyThrow(e);
}
}
@@ -1863,32 +1921,41 @@ public final class Parcel {
* @param msg The exception message.
*/
public final void readException(int code, String msg) {
+ SneakyThrow.sneakyThrow(createException(code, msg));
+ }
+
+ /**
+ * Creates an exception with the given message.
+ *
+ * @param code Used to determine which exception class to throw.
+ * @param msg The exception message.
+ */
+ private Exception createException(int code, String msg) {
switch (code) {
case EX_PARCELABLE:
if (readInt() > 0) {
- SneakyThrow.sneakyThrow(
- (Exception) readParcelable(Parcelable.class.getClassLoader()));
+ return (Exception) readParcelable(Parcelable.class.getClassLoader());
} else {
- throw new RuntimeException(msg + " [missing Parcelable]");
+ return new RuntimeException(msg + " [missing Parcelable]");
}
case EX_SECURITY:
- throw new SecurityException(msg);
+ return new SecurityException(msg);
case EX_BAD_PARCELABLE:
- throw new BadParcelableException(msg);
+ return new BadParcelableException(msg);
case EX_ILLEGAL_ARGUMENT:
- throw new IllegalArgumentException(msg);
+ return new IllegalArgumentException(msg);
case EX_NULL_POINTER:
- throw new NullPointerException(msg);
+ return new NullPointerException(msg);
case EX_ILLEGAL_STATE:
- throw new IllegalStateException(msg);
+ return new IllegalStateException(msg);
case EX_NETWORK_MAIN_THREAD:
- throw new NetworkOnMainThreadException();
+ return new NetworkOnMainThreadException();
case EX_UNSUPPORTED_OPERATION:
- throw new UnsupportedOperationException(msg);
+ return new UnsupportedOperationException(msg);
case EX_SERVICE_SPECIFIC:
- throw new ServiceSpecificException(readInt(), msg);
+ return new ServiceSpecificException(readInt(), msg);
}
- throw new RuntimeException("Unknown exception code: " + code
+ return new RuntimeException("Unknown exception code: " + code
+ " msg " + msg);
}
diff --git a/android/os/ParcelPerfTest.java b/android/os/ParcelPerfTest.java
index a92597f1..6e4c9c54 100644
--- a/android/os/ParcelPerfTest.java
+++ b/android/os/ParcelPerfTest.java
@@ -27,6 +27,10 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ParcelPerfTest {
@@ -167,4 +171,80 @@ public class ParcelPerfTest {
Parcel.obtain().recycle();
}
}
+
+ @Test
+ public void timeWriteException() {
+ timeWriteException(false);
+ }
+
+ @Test
+ public void timeWriteExceptionWithStackTraceParceling() {
+ timeWriteException(true);
+ }
+
+ @Test
+ public void timeReadException() {
+ timeReadException(false);
+ }
+
+ @Test
+ public void timeReadExceptionWithStackTraceParceling() {
+ timeReadException(true);
+ }
+
+ private void timeWriteException(boolean enableParceling) {
+ if (enableParceling) {
+ Parcel.setStackTraceParceling(true);
+ }
+ try {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ Parcel p = Parcel.obtain();
+ SecurityException e = new SecurityException("TestMessage");
+ while (state.keepRunning()) {
+ p.setDataPosition(0);
+ p.writeException(e);
+ }
+ } finally {
+ if (enableParceling) {
+ Parcel.setStackTraceParceling(false);
+ }
+ }
+ }
+
+ private void timeReadException(boolean enableParceling) {
+ if (enableParceling) {
+ Parcel.setStackTraceParceling(true);
+ }
+ try {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ Parcel p = Parcel.obtain();
+ String msg = "TestMessage";
+ p.writeException(new SecurityException(msg));
+ p.setDataPosition(0);
+ // First verify that remote cause is set (if parceling is enabled)
+ try {
+ p.readException();
+ } catch (SecurityException e) {
+ assertEquals(e.getMessage(), msg);
+ if (enableParceling) {
+ assertTrue(e.getCause() instanceof RemoteException);
+ } else {
+ assertNull(e.getCause());
+ }
+ }
+
+ while (state.keepRunning()) {
+ p.setDataPosition(0);
+ try {
+ p.readException();
+ } catch (SecurityException expected) {
+ }
+ }
+ } finally {
+ if (enableParceling) {
+ Parcel.setStackTraceParceling(false);
+ }
+ }
+ }
+
}
diff --git a/android/os/PowerManager.java b/android/os/PowerManager.java
index dd4825ef..068f5f7f 100644
--- a/android/os/PowerManager.java
+++ b/android/os/PowerManager.java
@@ -387,6 +387,12 @@ public final class PowerManager {
public static final int GO_TO_SLEEP_REASON_SLEEP_BUTTON = 6;
/**
+ * Go to sleep reason code: Going to sleep by request of an accessibility service
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_ACCESSIBILITY = 7;
+
+ /**
* Go to sleep flag: Skip dozing state and directly go to full sleep.
* @hide
*/
@@ -527,8 +533,7 @@ public final class PowerManager {
ServiceType.SOUND,
ServiceType.BATTERY_STATS,
ServiceType.DATA_SAVER,
- ServiceType.FORCE_ALL_APPS_STANDBY_JOBS,
- ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS,
+ ServiceType.FORCE_ALL_APPS_STANDBY,
ServiceType.OPTIONAL_SENSORS,
})
public @interface ServiceType {
@@ -545,14 +550,14 @@ public final class PowerManager {
int DATA_SAVER = 10;
/**
- * Whether the job scheduler should force app standby on all apps on battery saver or not.
+ * Whether to enable force-app-standby on all apps or not.
*/
- int FORCE_ALL_APPS_STANDBY_JOBS = 11;
+ int FORCE_ALL_APPS_STANDBY = 11;
/**
- * Whether the alarm manager should force app standby on all apps on battery saver or not.
+ * Whether to enable background check on all apps or not.
*/
- int FORCE_ALL_APPS_STANDBY_ALARMS = 12;
+ int FORCE_BACKGROUND_CHECK = 12;
/**
* Whether to disable non-essential sensors. (e.g. edge sensors.)
diff --git a/android/os/Process.java b/android/os/Process.java
index b5d62e55..0874d93e 100644
--- a/android/os/Process.java
+++ b/android/os/Process.java
@@ -151,6 +151,9 @@ public class Process {
*/
public static final int OTA_UPDATE_UID = 1061;
+ /** {@hide} */
+ public static final int NOBODY_UID = 9999;
+
/**
* Defines the start of a range of UIDs (and GIDs), going from this
* number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
diff --git a/android/os/RemoteException.java b/android/os/RemoteException.java
index 6d25fc17..4e8b9716 100644
--- a/android/os/RemoteException.java
+++ b/android/os/RemoteException.java
@@ -30,6 +30,12 @@ public class RemoteException extends AndroidException {
super(message);
}
+ /** @hide */
+ public RemoteException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
/** {@hide} */
public RuntimeException rethrowAsRuntimeException() {
throw new RuntimeException(this);
diff --git a/android/os/ShellCallback.java b/android/os/ShellCallback.java
index ad9fbfbf..6a62424c 100644
--- a/android/os/ShellCallback.java
+++ b/android/os/ShellCallback.java
@@ -105,6 +105,9 @@ public class ShellCallback implements Parcelable {
ShellCallback(Parcel in) {
mLocal = false;
mShellCallback = IShellCallback.Stub.asInterface(in.readStrongBinder());
+ if (mShellCallback != null) {
+ Binder.allowBlocking(mShellCallback.asBinder());
+ }
}
public static final Parcelable.Creator<ShellCallback> CREATOR
diff --git a/android/os/ShellCommand.java b/android/os/ShellCommand.java
index d75219fd..fa05a5e1 100644
--- a/android/os/ShellCommand.java
+++ b/android/os/ShellCommand.java
@@ -91,7 +91,13 @@ public abstract class ShellCommand {
mCmd = cmd;
mResultReceiver = resultReceiver;
- if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget);
+ if (DEBUG) {
+ RuntimeException here = new RuntimeException("here");
+ here.fillInStackTrace();
+ Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
+ Slog.d(TAG, "Calling uid=" + Binder.getCallingUid()
+ + " pid=" + Binder.getCallingPid() + " ShellCallback=" + getShellCallback());
+ }
int res = -1;
try {
res = onCommand(mCmd);
@@ -227,15 +233,19 @@ public abstract class ShellCommand {
* @hide
*/
public ParcelFileDescriptor openFileForSystem(String path, String mode) {
+ if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode);
try {
ParcelFileDescriptor pfd = getShellCallback().openFile(path,
"u:r:system_server:s0", mode);
if (pfd != null) {
+ if (DEBUG) Slog.d(TAG, "Got file: " + pfd);
return pfd;
}
} catch (RuntimeException e) {
+ if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage());
getErrPrintWriter().println("Failure opening file: " + e.getMessage());
}
+ if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path);
getErrPrintWriter().println("Error: Unable to open file: " + path);
getErrPrintWriter().println("Consider using a file under /data/local/tmp/");
return null;
diff --git a/android/os/UEventObserver.java b/android/os/UEventObserver.java
index 5c80ca65..69a39225 100644
--- a/android/os/UEventObserver.java
+++ b/android/os/UEventObserver.java
@@ -108,7 +108,7 @@ public abstract class UEventObserver {
* UEventObserver after this call. Repeated calls have no effect.
*/
public final void stopObserving() {
- final UEventThread t = getThread();
+ final UEventThread t = peekThread();
if (t != null) {
t.removeObserver(this);
}
diff --git a/android/os/storage/StorageManager.java b/android/os/storage/StorageManager.java
index 0b007ddf..4796712f 100644
--- a/android/os/storage/StorageManager.java
+++ b/android/os/storage/StorageManager.java
@@ -1123,6 +1123,15 @@ public class StorageManager {
return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace());
}
+ /** {@hide} */
+ public void mkdirs(File file) {
+ try {
+ mStorageManager.mkdirs(mContext.getOpPackageName(), file.getAbsolutePath());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @removed */
public @NonNull StorageVolume[] getVolumeList() {
return getVolumeList(mContext.getUserId(), 0);
diff --git a/android/provider/SearchIndexableData.java b/android/provider/SearchIndexableData.java
index 5e0a76de..a60be536 100644
--- a/android/provider/SearchIndexableData.java
+++ b/android/provider/SearchIndexableData.java
@@ -56,6 +56,8 @@ public abstract class SearchIndexableData {
/**
* The key for the data. This is application specific. Should be unique per data as the data
* should be able to be retrieved by the key.
+ * <p/>
+ * This is required for indexing to work.
*/
public String key;
diff --git a/android/provider/SearchIndexablesContract.java b/android/provider/SearchIndexablesContract.java
index ff8b9dd7..adf437ce 100644
--- a/android/provider/SearchIndexablesContract.java
+++ b/android/provider/SearchIndexablesContract.java
@@ -62,11 +62,25 @@ public class SearchIndexablesContract {
public static final String NON_INDEXABLES_KEYS = "non_indexables_key";
/**
+ * Site map pairs data key
+ *
+ * @hide
+ */
+ public static final String SITE_MAP_PAIRS_KEYS = "site_map_pairs";
+
+ /**
* ContentProvider path for non indexable data keys.
*/
public static final String NON_INDEXABLES_KEYS_PATH = SETTINGS + "/" + NON_INDEXABLES_KEYS;
/**
+ * ContentProvider path for sitemap keys.
+ *
+ * @hide
+ */
+ public static final String SITE_MAP_PAIRS_PATH = SETTINGS + "/" + SITE_MAP_PAIRS_KEYS;
+
+ /**
* Indexable xml resources columns.
*/
public static final String[] INDEXABLES_XML_RES_COLUMNS = new String[] {
@@ -113,6 +127,18 @@ public class SearchIndexablesContract {
};
/**
+ * Columns for site map queries.
+ *
+ * @hide
+ */
+ public static final String[] SITE_MAP_COLUMNS = new String[] {
+ SiteMapColumns.PARENT_CLASS,
+ SiteMapColumns.PARENT_TITLE,
+ SiteMapColumns.CHILD_CLASS,
+ SiteMapColumns.CHILD_TITLE,
+ };
+
+ /**
* Indexable raw data columns indices.
*/
public static final int COLUMN_INDEX_RAW_RANK = 0;
@@ -169,6 +195,16 @@ public class SearchIndexablesContract {
}
/**
+ * @hide
+ */
+ public static final class SiteMapColumns {
+ public static final String PARENT_CLASS = "parent_class";
+ public static final String CHILD_CLASS = "child_class";
+ public static final String PARENT_TITLE = "parent_title";
+ public static final String CHILD_TITLE = "child_title";
+ }
+
+ /**
* Constants related to a {@link SearchIndexableData}.
*
* This is the raw data that is stored into an Index. This is related to
diff --git a/android/provider/SearchIndexablesProvider.java b/android/provider/SearchIndexablesProvider.java
index 3120e543..138e77b6 100644
--- a/android/provider/SearchIndexablesProvider.java
+++ b/android/provider/SearchIndexablesProvider.java
@@ -72,6 +72,7 @@ public abstract class SearchIndexablesProvider extends ContentProvider {
private static final int MATCH_RES_CODE = 1;
private static final int MATCH_RAW_CODE = 2;
private static final int MATCH_NON_INDEXABLE_KEYS_CODE = 3;
+ private static final int MATCH_SITE_MAP_PAIRS_CODE = 4;
/**
* Implementation is provided by the parent class.
@@ -87,6 +88,8 @@ public abstract class SearchIndexablesProvider extends ContentProvider {
MATCH_RAW_CODE);
mMatcher.addURI(mAuthority, SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH,
MATCH_NON_INDEXABLE_KEYS_CODE);
+ mMatcher.addURI(mAuthority, SearchIndexablesContract.SITE_MAP_PAIRS_PATH,
+ MATCH_SITE_MAP_PAIRS_CODE);
// Sanity check our setup
if (!info.exported) {
@@ -112,6 +115,8 @@ public abstract class SearchIndexablesProvider extends ContentProvider {
return queryRawData(null);
case MATCH_NON_INDEXABLE_KEYS_CODE:
return queryNonIndexableKeys(null);
+ case MATCH_SITE_MAP_PAIRS_CODE:
+ return querySiteMapPairs();
default:
throw new UnsupportedOperationException("Unknown Uri " + uri);
}
@@ -150,6 +155,17 @@ public abstract class SearchIndexablesProvider extends ContentProvider {
*/
public abstract Cursor queryNonIndexableKeys(String[] projection);
+ /**
+ * Returns a {@link Cursor}, where rows are [parent class, child class] entries to form a site
+ * map. The list of pairs should be as complete as possible.
+ *
+ * @hide
+ */
+ public Cursor querySiteMapPairs() {
+ // By default no-op.
+ return null;
+ }
+
@Override
public String getType(Uri uri) {
switch (mMatcher.match(uri)) {
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index 6decc305..1e0948a4 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -9379,6 +9379,9 @@ public final class Settings {
/** {@hide} */
public static final String
BLUETOOTH_PAN_PRIORITY_PREFIX = "bluetooth_pan_priority_";
+ /** {@hide} */
+ public static final String
+ BLUETOOTH_HEARING_AID_PRIORITY_PREFIX = "bluetooth_hearing_aid_priority_";
/**
* Activity manager specific settings.
@@ -9745,6 +9748,14 @@ public final class Settings {
}
/**
+ * Get the key that retrieves a bluetooth hearing aid priority.
+ * @hide
+ */
+ public static final String getBluetoothHearingAidPriorityKey(String address) {
+ return BLUETOOTH_HEARING_AID_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
* Get the key that retrieves a bluetooth map priority.
* @hide
*/
@@ -9854,6 +9865,27 @@ public final class Settings {
public static final String WAIT_FOR_DEBUGGER = "wait_for_debugger";
/**
+ * Allow GPU debug layers?
+ * 0 = no
+ * 1 = yes
+ * @hide
+ */
+ public static final String ENABLE_GPU_DEBUG_LAYERS = "enable_gpu_debug_layers";
+
+ /**
+ * App allowed to load GPU debug layers
+ * @hide
+ */
+ public static final String GPU_DEBUG_APP = "gpu_debug_app";
+
+ /**
+ * Ordered GPU debug layer list
+ * i.e. <layer1>:<layer2>:...:<layerN>
+ * @hide
+ */
+ public static final String GPU_DEBUG_LAYERS = "gpu_debug_layers";
+
+ /**
* Control whether the process CPU usage meter should be shown.
*
* @deprecated This functionality is no longer available as of
@@ -10424,6 +10456,15 @@ public final class Settings {
"storage_settings_clobber_threshold";
/**
+ * If set to 1, {@link Secure#LOCATION_MODE} will be set to {@link Secure#LOCATION_MODE_OFF}
+ * temporarily for all users.
+ *
+ * @hide
+ */
+ public static final String LOCATION_GLOBAL_KILL_SWITCH =
+ "location_global_kill_switch";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
diff --git a/android/security/KeyChain.java b/android/security/KeyChain.java
index 3fe730fd..2daf733d 100644
--- a/android/security/KeyChain.java
+++ b/android/security/KeyChain.java
@@ -40,6 +40,7 @@ import android.security.keystore.KeyProperties;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
+import java.security.KeyPair;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
@@ -418,6 +419,18 @@ public final class KeyChain {
@Nullable @WorkerThread
public static PrivateKey getPrivateKey(@NonNull Context context, @NonNull String alias)
throws KeyChainException, InterruptedException {
+ KeyPair keyPair = getKeyPair(context, alias);
+ if (keyPair != null) {
+ return keyPair.getPrivate();
+ }
+
+ return null;
+ }
+
+ /** @hide */
+ @Nullable @WorkerThread
+ public static KeyPair getKeyPair(@NonNull Context context, @NonNull String alias)
+ throws KeyChainException, InterruptedException {
if (alias == null) {
throw new NullPointerException("alias == null");
}
@@ -439,7 +452,7 @@ public final class KeyChain {
return null;
} else {
try {
- return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
+ return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
KeyStore.getInstance(), keyId, KeyStore.UID_SELF);
} catch (RuntimeException | UnrecoverableKeyException e) {
throw new KeyChainException(e);
diff --git a/android/service/autofill/AutofillService.java b/android/service/autofill/AutofillService.java
index 953501c7..cd362c71 100644
--- a/android/service/autofill/AutofillService.java
+++ b/android/service/autofill/AutofillService.java
@@ -438,8 +438,21 @@ import com.android.internal.os.SomeArgs;
* AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
*
* save(username, password);
- *
* </pre>
+ *
+ *
+ * <a name="Privacy"></a>
+ * <h3>Privacy</h3>
+ *
+ * <p>The {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} method is called
+ * without the user content. The Android system strips some properties of the
+ * {@link android.app.assist.AssistStructure.ViewNode view nodes} passed to these calls, but not all
+ * of them. For example, the data provided in the {@link android.view.ViewStructure.HtmlInfo}
+ * objects set by {@link android.webkit.WebView} is never stripped out.
+ *
+ * <p>Because this data could contain PII (Personally Identifiable Information, such as username or
+ * email address), the service should only use it locally (i.e., in the app's process) for
+ * heuristics purposes, but it should not be sent to external servers.
*/
public abstract class AutofillService extends Service {
private static final String TAG = "AutofillService";
diff --git a/android/service/autofill/Dataset.java b/android/service/autofill/Dataset.java
index 331130e7..cb20e71b 100644
--- a/android/service/autofill/Dataset.java
+++ b/android/service/autofill/Dataset.java
@@ -216,7 +216,12 @@ public final class Dataset implements Parcelable {
}
/**
- * Requires a dataset authentication before autofilling the activity with this dataset.
+ * Triggers a custom UI before before autofilling the screen with the contents of this
+ * dataset.
+ *
+ * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
+ * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
+ * for examples.
*
* <p>This method is called when you need to provide an authentication
* UI for the data set. For example, when a data set contains credit card information
@@ -335,9 +340,11 @@ public final class Dataset implements Parcelable {
/**
* Sets the value of a field using an <a href="#Filtering">explicit filter</a>.
*
- * <p>This method is typically used when the dataset is not authenticated and the field
- * value is not {@link AutofillValue#isText() text} but the service still wants to allow
- * the user to filter it out.
+ * <p>This method is typically used when the dataset is authenticated and the service
+ * does not know its value but wants to hide the dataset after the user enters a minimum
+ * number of characters. For example, if the dataset represents a credit card number and the
+ * service does not want to show the "Tap to authenticate" message until the user tapped
+ * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
*
* @param id id returned by {@link
* android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
@@ -364,11 +371,11 @@ public final class Dataset implements Parcelable {
* Sets the value of a field, using a custom {@link RemoteViews presentation} to
* visualize it and a <a href="#Filtering">explicit filter</a>.
*
- * <p>Typically used to allow filtering on
- * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated datasets}. For
- * example, if the dataset represents a credit card number and the service does not want to
- * show the "Tap to authenticate" message until the user tapped 4 digits, in which case
- * the filter would be {@code Pattern.compile("\\d.{4,}")}.
+ * <p>This method is typically used when the dataset is authenticated and the service
+ * does not know its value but wants to hide the dataset after the user enters a minimum
+ * number of characters. For example, if the dataset represents a credit card number and the
+ * service does not want to show the "Tap to authenticate" message until the user tapped
+ * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
*
* @param id id returned by {@link
* android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
diff --git a/android/service/autofill/FillResponse.java b/android/service/autofill/FillResponse.java
index 4e6a8845..84a0974d 100644
--- a/android/service/autofill/FillResponse.java
+++ b/android/service/autofill/FillResponse.java
@@ -180,8 +180,12 @@ public final class FillResponse implements Parcelable {
private boolean mDestroyed;
/**
- * Requires a fill response authentication before autofilling the screen with
- * any data set in this response.
+ * Triggers a custom UI before before autofilling the screen with any data set in this
+ * response.
+ *
+ * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
+ * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
+ * for examples.
*
* <p>This is typically useful when a user interaction is required to unlock their
* data vault if you encrypt the data set labels and data set data. It is recommended
@@ -319,6 +323,7 @@ public final class FillResponse implements Parcelable {
*/
public Builder setClientState(@Nullable Bundle clientState) {
throwIfDestroyed();
+ throwIfDisableAutofillCalled();
mClientState = clientState;
return this;
}
@@ -385,8 +390,9 @@ public final class FillResponse implements Parcelable {
*
* @throws IllegalArgumentException if {@code duration} is not a positive number.
* @throws IllegalStateException if either {@link #addDataset(Dataset)},
- * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, or
- * {@link #setSaveInfo(SaveInfo)} was already called.
+ * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
+ * {@link #setSaveInfo(SaveInfo)}, or {@link #setClientState(Bundle)}
+ * was already called.
*/
public Builder disableAutofill(long duration) {
throwIfDestroyed();
@@ -394,7 +400,7 @@ public final class FillResponse implements Parcelable {
throw new IllegalArgumentException("duration must be greater than 0");
}
if (mAuthentication != null || mDatasets != null || mSaveInfo != null
- || mFieldsDetection != null) {
+ || mFieldsDetection != null || mClientState != null) {
throw new IllegalStateException("disableAutofill() must be the only method called");
}
@@ -410,7 +416,8 @@ public final class FillResponse implements Parcelable {
* <li>{@link #build()} was already called.
* <li>No call was made to {@link #addDataset(Dataset)},
* {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
- * {@link #setSaveInfo(SaveInfo)}, or {@link #disableAutofill(long)}.
+ * {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)},
+ * or {@link #setClientState(Bundle)}.
* </ol>
*
* @return A built response.
@@ -418,10 +425,10 @@ public final class FillResponse implements Parcelable {
public FillResponse build() {
throwIfDestroyed();
if (mAuthentication == null && mDatasets == null && mSaveInfo == null
- && mDisableDuration == 0 && mFieldsDetection == null) {
+ && mDisableDuration == 0 && mFieldsDetection == null && mClientState == null) {
throw new IllegalStateException("need to provide: at least one DataSet, or a "
+ "SaveInfo, or an authentication with a presentation, "
- + "or a FieldsDetection, or disable autofill");
+ + "or a FieldsDetection, or a client state, or disable autofill");
}
mDestroyed = true;
return new FillResponse(this);
diff --git a/android/service/autofill/InternalSanitizer.java b/android/service/autofill/InternalSanitizer.java
index 95d2f660..d77e41e3 100644
--- a/android/service/autofill/InternalSanitizer.java
+++ b/android/service/autofill/InternalSanitizer.java
@@ -16,6 +16,7 @@
package android.service.autofill;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.Parcelable;
import android.view.autofill.AutofillValue;
@@ -32,7 +33,11 @@ public abstract class InternalSanitizer implements Sanitizer, Parcelable {
/**
* Sanitizes an {@link AutofillValue}.
*
+ * @return sanitized value or {@code null} if value could not be sanitized (for example: didn't
+ * match regex, it's an invalid type, regex failed, etc).
+ *
* @hide
*/
+ @Nullable
public abstract AutofillValue sanitize(@NonNull AutofillValue value);
}
diff --git a/android/service/autofill/NegationValidator.java b/android/service/autofill/NegationValidator.java
new file mode 100644
index 00000000..a963f9f9
--- /dev/null
+++ b/android/service/autofill/NegationValidator.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Validator used to implement a {@code NOT} logical operation.
+ *
+ * @hide
+ */
+final class NegationValidator extends InternalValidator {
+ @NonNull private final InternalValidator mValidator;
+
+ NegationValidator(@NonNull InternalValidator validator) {
+ mValidator = Preconditions.checkNotNull(validator);
+ }
+
+ @Override
+ public boolean isValid(@NonNull ValueFinder finder) {
+ return !mValidator.isValid(finder);
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ return "NegationValidator: [validator=" + mValidator + "]";
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mValidator, flags);
+ }
+
+ public static final Parcelable.Creator<NegationValidator> CREATOR =
+ new Parcelable.Creator<NegationValidator>() {
+ @Override
+ public NegationValidator createFromParcel(Parcel parcel) {
+ return new NegationValidator(parcel.readParcelable(null));
+ }
+
+ @Override
+ public NegationValidator[] newArray(int size) {
+ return new NegationValidator[size];
+ }
+ };
+}
diff --git a/android/service/autofill/SaveCallback.java b/android/service/autofill/SaveCallback.java
index 7207f1df..855981a5 100644
--- a/android/service/autofill/SaveCallback.java
+++ b/android/service/autofill/SaveCallback.java
@@ -16,9 +16,14 @@
package android.service.autofill;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Activity;
+import android.content.IntentSender;
import android.os.RemoteException;
+import com.android.internal.util.Preconditions;
+
/**
* Handles save requests from the {@link AutofillService} into the {@link Activity} being
* autofilled.
@@ -36,18 +41,33 @@ public final class SaveCallback {
* Notifies the Android System that an
* {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully handled
* by the service.
+ */
+ public void onSuccess() {
+ onSuccessInternal(null);
+ }
+
+ /**
+ * Notifies the Android System that an
+ * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully handled
+ * by the service.
*
- * <p>If the service could not handle the request right away&mdash;for example, because it must
- * launch an activity asking the user to authenticate first or because the network is
- * down&mdash;it should still call {@link #onSuccess()}.
+ * <p>This method is useful when the service requires extra work&mdash;for example, launching an
+ * activity asking the user to authenticate first &mdash;before it can process the request,
+ * as the intent will be launched from the context of the activity being autofilled and hence
+ * will be part of that activity's stack.
*
- * @throws RuntimeException if an error occurred while calling the Android System.
+ * @param intentSender intent that will be launched from the context of activity being
+ * autofilled.
*/
- public void onSuccess() {
+ public void onSuccess(@NonNull IntentSender intentSender) {
+ onSuccessInternal(Preconditions.checkNotNull(intentSender));
+ }
+
+ private void onSuccessInternal(@Nullable IntentSender intentSender) {
assertNotCalled();
mCalled = true;
try {
- mCallback.onSuccess();
+ mCallback.onSuccess(intentSender);
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
@@ -63,11 +83,10 @@ public final class SaveCallback {
* the {@link SaveRequest} and call {@link #onSuccess()} instead.
*
* <p><b>Note:</b> The Android System displays an UI with the supplied error message; if
- * you prefer to show your own message, call {@link #onSuccess()} instead.
+ * you prefer to show your own message, call {@link #onSuccess()} or
+ * {@link #onSuccess(IntentSender)} instead.
*
* @param message error message to be displayed to the user.
- *
- * @throws RuntimeException if an error occurred while calling the Android System.
*/
public void onFailure(CharSequence message) {
assertNotCalled();
diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java
index 9a1dcbb2..0b50f074 100644
--- a/android/service/autofill/SaveInfo.java
+++ b/android/service/autofill/SaveInfo.java
@@ -599,9 +599,9 @@ public final class SaveInfo implements Parcelable {
* credit card number:
*
* <pre class="prettyprint">
- * builder.addSanitizer(
- * new TextValueSanitizer(Pattern.compile("^(\\d{4}\s?\\d{4}\s?\\d{4}\s?\\d{4})$"),
- * "$1$2$3$4"), ccNumberId);
+ * builder.addSanitizer(new TextValueSanitizer(
+ * Pattern.compile("^(\\d{4})\\s?(\\d{4})\\s?(\\d{4})\\s?(\\d{4})$", "$1$2$3$4")),
+ * ccNumberId);
* </pre>
*
* <p>The same sanitizer can be reused to sanitize multiple fields. For example, to trim
@@ -613,6 +613,11 @@ public final class SaveInfo implements Parcelable {
* usernameId, passwordId);
* </pre>
*
+ * <p>The sanitizer can also be used as an alternative for a
+ * {@link #setValidator(Validator) validator}. If any of the {@code ids} is a
+ * {@link #SaveInfo.Builder(int, AutofillId[]) required id} and the {@code sanitizer} fails
+ * because of it, then the save UI is not shown.
+ *
* @param sanitizer an implementation provided by the Android System.
* @param ids id of fields whose value will be sanitized.
* @return this builder.
diff --git a/android/service/autofill/TextValueSanitizer.java b/android/service/autofill/TextValueSanitizer.java
index 12e85b1d..e5ad77a1 100644
--- a/android/service/autofill/TextValueSanitizer.java
+++ b/android/service/autofill/TextValueSanitizer.java
@@ -19,6 +19,7 @@ package android.service.autofill;
import static android.view.autofill.Helper.sDebug;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -36,7 +37,8 @@ import java.util.regex.Pattern;
* <p>For example, to remove spaces from groups of 4-digits in a credit card:
*
* <pre class="prettyprint">
- * new TextValueSanitizer(Pattern.compile("^(\\d{4}\s?\\d{4}\s?\\d{4}\s?\\d{4})$"), "$1$2$3$4")
+ * new TextValueSanitizer(Pattern.compile("^(\\d{4})\\s?(\\d{4})\\s?(\\d{4})\\s?(\\d{4})$",
+ * "$1$2$3$4")
* </pre>
*/
public final class TextValueSanitizer extends InternalSanitizer implements
@@ -62,24 +64,31 @@ public final class TextValueSanitizer extends InternalSanitizer implements
/** @hide */
@Override
@TestApi
+ @Nullable
public AutofillValue sanitize(@NonNull AutofillValue value) {
if (value == null) {
Slog.w(TAG, "sanitize() called with null value");
return null;
}
- if (!value.isText()) return value;
+ if (!value.isText()) {
+ if (sDebug) Slog.d(TAG, "sanitize() called with non-text value: " + value);
+ return null;
+ }
final CharSequence text = value.getTextValue();
try {
final Matcher matcher = mRegex.matcher(text);
- if (!matcher.matches()) return value;
+ if (!matcher.matches()) {
+ if (sDebug) Slog.d(TAG, "sanitize(): " + mRegex + " failed for " + value);
+ return null;
+ }
final CharSequence sanitized = matcher.replaceAll(mSubst);
return AutofillValue.forText(sanitized);
} catch (Exception e) {
Slog.w(TAG, "Exception evaluating " + mRegex + "/" + mSubst + ": " + e);
- return value;
+ return null;
}
}
diff --git a/android/service/autofill/Validators.java b/android/service/autofill/Validators.java
index 51b503c2..1c838687 100644
--- a/android/service/autofill/Validators.java
+++ b/android/service/autofill/Validators.java
@@ -52,6 +52,19 @@ public final class Validators {
return new OptionalValidators(getInternalValidators(validators));
}
+ /**
+ * Creates a validator that is valid only if {@code validator} is not.
+ *
+ * @throws IllegalArgumentException if {@code validator} is an instance of a class that is not
+ * provided by the Android System.
+ */
+ @NonNull
+ public static Validator not(@NonNull Validator validator) {
+ Preconditions.checkArgument(validator instanceof InternalValidator,
+ "validator not provided by Android System: " + validator);
+ return new NegationValidator((InternalValidator) validator);
+ }
+
private static InternalValidator[] getInternalValidators(Validator[] validators) {
Preconditions.checkArrayElementsNotNull(validators, "validators");
diff --git a/android/service/euicc/EuiccService.java b/android/service/euicc/EuiccService.java
index cd233b83..df0842f7 100644
--- a/android/service/euicc/EuiccService.java
+++ b/android/service/euicc/EuiccService.java
@@ -105,6 +105,13 @@ public abstract class EuiccService extends Service {
public static final String EXTRA_RESOLUTION_CALLING_PACKAGE =
"android.service.euicc.extra.RESOLUTION_CALLING_PACKAGE";
+ /**
+ * Intent extra set for resolution requests containing a boolean indicating whether to ask the
+ * user to retry another confirmation code.
+ */
+ public static final String EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED =
+ "android.service.euicc.extra.RESOLUTION_CONFIRMATION_CODE_RETRIED";
+
/** Result code for a successful operation. */
public static final int RESULT_OK = 0;
/** Result code indicating that an active SIM must be deactivated to perform the operation. */
diff --git a/android/service/wallpaper/WallpaperService.java b/android/service/wallpaper/WallpaperService.java
index 1c6275fb..dd0ae339 100644
--- a/android/service/wallpaper/WallpaperService.java
+++ b/android/service/wallpaper/WallpaperService.java
@@ -889,7 +889,8 @@ public abstract class WallpaperService extends Service {
mFinalStableInsets.set(mDispatchedStableInsets);
WindowInsets insets = new WindowInsets(mFinalSystemInsets,
null, mFinalStableInsets,
- getResources().getConfiguration().isScreenRound(), false);
+ getResources().getConfiguration().isScreenRound(), false,
+ null /* displayCutout */);
if (DEBUG) {
Log.v(TAG, "dispatching insets=" + insets);
}
diff --git a/android/support/LibraryGroups.java b/android/support/LibraryGroups.java
index feaefbc6..19c0a927 100644
--- a/android/support/LibraryGroups.java
+++ b/android/support/LibraryGroups.java
@@ -27,4 +27,5 @@ public class LibraryGroups {
public static final String ARCH_CORE = "android.arch.core";
public static final String PAGING = "android.arch.paging";
public static final String NAVIGATION = "android.arch.navigation";
+ public static final String SLICES = "androidx.app.slice";
}
diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java
index 4695c45c..67a6247a 100644
--- a/android/support/car/widget/PagedListView.java
+++ b/android/support/car/widget/PagedListView.java
@@ -286,38 +286,6 @@ public class PagedListView extends FrameLayout {
return mLayoutManager.getPosition(v);
}
- private void scroll(int direction) {
- View focusedView = mRecyclerView.getFocusedChild();
- if (focusedView != null) {
- int position = mLayoutManager.getPosition(focusedView);
- int newPosition =
- Math.max(Math.min(position + direction, mLayoutManager.getItemCount() - 1), 0);
- if (newPosition != position) {
- // newPosition/position are adapter positions.
- // Convert to layout position by subtracting adapter position of view at layout
- // position 0.
- View childAt = mRecyclerView.getChildAt(
- newPosition - mLayoutManager.getPosition(mLayoutManager.getChildAt(0)));
- if (childAt != null) {
- childAt.requestFocus();
- }
- }
- }
- }
-
- private boolean canScroll(int direction) {
- View focusedView = mRecyclerView.getFocusedChild();
- if (focusedView != null) {
- int position = mLayoutManager.getPosition(focusedView);
- int newPosition =
- Math.max(Math.min(position + direction, mLayoutManager.getItemCount() - 1), 0);
- if (newPosition != position) {
- return true;
- }
- }
- return false;
- }
-
@NonNull
public CarRecyclerView getRecyclerView() {
return mRecyclerView;
diff --git a/android/support/design/widget/BottomSheetBehavior.java b/android/support/design/widget/BottomSheetBehavior.java
index aaa9b804..00ce8f90 100644
--- a/android/support/design/widget/BottomSheetBehavior.java
+++ b/android/support/design/widget/BottomSheetBehavior.java
@@ -559,7 +559,7 @@ public class BottomSheetBehavior<V extends View> extends CoordinatorLayout.Behav
* Gets the current state of the bottom sheet.
*
* @return One of {@link #STATE_EXPANDED}, {@link #STATE_COLLAPSED}, {@link #STATE_DRAGGING},
- * and {@link #STATE_SETTLING}.
+ * {@link #STATE_SETTLING}, and {@link #STATE_HIDDEN}.
*/
@State
public final int getState() {
diff --git a/android/support/design/widget/CollapsingToolbarLayout.java b/android/support/design/widget/CollapsingToolbarLayout.java
index 0051de9e..8c9b7d49 100644
--- a/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/android/support/design/widget/CollapsingToolbarLayout.java
@@ -44,6 +44,7 @@ import android.support.v4.util.ObjectsCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
+import android.support.v4.widget.ViewGroupUtils;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.AttributeSet;
diff --git a/android/support/design/widget/CoordinatorLayout.java b/android/support/design/widget/CoordinatorLayout.java
index 477a8d62..c45810ef 100644
--- a/android/support/design/widget/CoordinatorLayout.java
+++ b/android/support/design/widget/CoordinatorLayout.java
@@ -56,6 +56,8 @@ import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewCompat.NestedScrollType;
import android.support.v4.view.ViewCompat.ScrollAxis;
import android.support.v4.view.WindowInsetsCompat;
+import android.support.v4.widget.DirectedAcyclicGraph;
+import android.support.v4.widget.ViewGroupUtils;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
diff --git a/android/support/design/widget/DirectedAcyclicGraphTest.java b/android/support/design/widget/DirectedAcyclicGraphTest.java
deleted file mode 100644
index ec7687d5..00000000
--- a/android/support/design/widget/DirectedAcyclicGraphTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.design.widget;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.support.annotation.NonNull;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class DirectedAcyclicGraphTest {
-
- private DirectedAcyclicGraph<TestNode> mGraph;
-
- @Before
- public void setup() {
- mGraph = new DirectedAcyclicGraph<>();
- }
-
- @Test
- public void test_addNode() {
- final TestNode node = new TestNode("node");
- mGraph.addNode(node);
- assertEquals(1, mGraph.size());
- assertTrue(mGraph.contains(node));
- }
-
- @Test
- public void test_addNodeAgain() {
- final TestNode node = new TestNode("node");
- mGraph.addNode(node);
- mGraph.addNode(node);
-
- assertEquals(1, mGraph.size());
- assertTrue(mGraph.contains(node));
- }
-
- @Test
- public void test_addEdge() {
- final TestNode node = new TestNode("node");
- final TestNode edge = new TestNode("edge");
-
- mGraph.addNode(node);
- mGraph.addNode(edge);
- mGraph.addEdge(node, edge);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void test_addEdgeWithNotAddedEdgeNode() {
- final TestNode node = new TestNode("node");
- final TestNode edge = new TestNode("edge");
-
- // Add the node, but not the edge node
- mGraph.addNode(node);
-
- // Now add the link
- mGraph.addEdge(node, edge);
- }
-
- @Test
- public void test_getIncomingEdges() {
- final TestNode node = new TestNode("node");
- final TestNode edge = new TestNode("edge");
- mGraph.addNode(node);
- mGraph.addNode(edge);
- mGraph.addEdge(node, edge);
-
- final List<TestNode> incomingEdges = mGraph.getIncomingEdges(node);
- assertNotNull(incomingEdges);
- assertEquals(1, incomingEdges.size());
- assertEquals(edge, incomingEdges.get(0));
- }
-
- @Test
- public void test_getOutgoingEdges() {
- final TestNode node = new TestNode("node");
- final TestNode edge = new TestNode("edge");
- mGraph.addNode(node);
- mGraph.addNode(edge);
- mGraph.addEdge(node, edge);
-
- // Now assert the getOutgoingEdges returns a list which has one element (node)
- final List<TestNode> outgoingEdges = mGraph.getOutgoingEdges(edge);
- assertNotNull(outgoingEdges);
- assertEquals(1, outgoingEdges.size());
- assertTrue(outgoingEdges.contains(node));
- }
-
- @Test
- public void test_getOutgoingEdgesMultiple() {
- final TestNode node1 = new TestNode("1");
- final TestNode node2 = new TestNode("2");
- final TestNode edge = new TestNode("edge");
- mGraph.addNode(node1);
- mGraph.addNode(node2);
- mGraph.addNode(edge);
-
- mGraph.addEdge(node1, edge);
- mGraph.addEdge(node2, edge);
-
- // Now assert the getOutgoingEdges returns a list which has 2 elements (node1 & node2)
- final List<TestNode> outgoingEdges = mGraph.getOutgoingEdges(edge);
- assertNotNull(outgoingEdges);
- assertEquals(2, outgoingEdges.size());
- assertTrue(outgoingEdges.contains(node1));
- assertTrue(outgoingEdges.contains(node2));
- }
-
- @Test
- public void test_hasOutgoingEdges() {
- final TestNode node = new TestNode("node");
- final TestNode edge = new TestNode("edge");
- mGraph.addNode(node);
- mGraph.addNode(edge);
-
- // There is no edge currently and assert that fact
- assertFalse(mGraph.hasOutgoingEdges(edge));
- // Now add the edge
- mGraph.addEdge(node, edge);
- // and assert that the methods returns true;
- assertTrue(mGraph.hasOutgoingEdges(edge));
- }
-
- @Test
- public void test_clear() {
- final TestNode node1 = new TestNode("1");
- final TestNode node2 = new TestNode("2");
- final TestNode edge = new TestNode("edge");
- mGraph.addNode(node1);
- mGraph.addNode(node2);
- mGraph.addNode(edge);
-
- // Now clear the graph
- mGraph.clear();
-
- // Now assert the graph is empty and that contains returns false
- assertEquals(0, mGraph.size());
- assertFalse(mGraph.contains(node1));
- assertFalse(mGraph.contains(node2));
- assertFalse(mGraph.contains(edge));
- }
-
- @Test
- public void test_getSortedList() {
- final TestNode node1 = new TestNode("A");
- final TestNode node2 = new TestNode("B");
- final TestNode node3 = new TestNode("C");
- final TestNode node4 = new TestNode("D");
-
- // Now we'll add the nodes
- mGraph.addNode(node1);
- mGraph.addNode(node2);
- mGraph.addNode(node3);
- mGraph.addNode(node4);
-
- // Now we'll add edges so that 4 <- 2, 2 <- 3, 3 <- 1 (where <- denotes a dependency)
- mGraph.addEdge(node4, node2);
- mGraph.addEdge(node2, node3);
- mGraph.addEdge(node3, node1);
-
- final List<TestNode> sorted = mGraph.getSortedList();
- // Assert that it is the correct size
- assertEquals(4, sorted.size());
- // Assert that all of the nodes are present and in their sorted order
- assertEquals(node1, sorted.get(0));
- assertEquals(node3, sorted.get(1));
- assertEquals(node2, sorted.get(2));
- assertEquals(node4, sorted.get(3));
- }
-
- private static class TestNode {
- private final String mLabel;
-
- TestNode(@NonNull String label) {
- mLabel = label;
- }
-
- @Override
- public String toString() {
- return "TestNode: " + mLabel;
- }
- }
-
-}
diff --git a/android/support/design/widget/FloatingActionButton.java b/android/support/design/widget/FloatingActionButton.java
index b9388366..f37b3798 100644
--- a/android/support/design/widget/FloatingActionButton.java
+++ b/android/support/design/widget/FloatingActionButton.java
@@ -36,6 +36,7 @@ import android.support.annotation.VisibleForTesting;
import android.support.design.R;
import android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener;
import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ViewGroupUtils;
import android.support.v7.widget.AppCompatImageHelper;
import android.util.AttributeSet;
import android.util.Log;
@@ -116,6 +117,11 @@ public class FloatingActionButton extends VisibilityAwareImageButton {
public static final int SIZE_AUTO = -1;
/**
+ * Indicates that FloatingActionButton should not have a custom size.
+ */
+ public static final int NO_CUSTOM_SIZE = 0;
+
+ /**
* The switch point for the largest screen edge where SIZE_AUTO switches from mini to normal.
*/
private static final int AUTO_MINI_LARGEST_SCREEN_WIDTH = 470;
@@ -132,6 +138,7 @@ public class FloatingActionButton extends VisibilityAwareImageButton {
private int mBorderWidth;
private int mRippleColor;
private int mSize;
+ private int mCustomSize;
int mImagePadding;
private int mMaxImageSize;
@@ -164,6 +171,8 @@ public class FloatingActionButton extends VisibilityAwareImageButton {
R.styleable.FloatingActionButton_backgroundTintMode, -1), null);
mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0);
mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_AUTO);
+ mCustomSize = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fabCustomSize,
+ 0);
mBorderWidth = a.getDimensionPixelSize(R.styleable.FloatingActionButton_borderWidth, 0);
final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f);
final float pressedTranslationZ = a.getDimension(
@@ -430,12 +439,41 @@ public class FloatingActionButton extends VisibilityAwareImageButton {
};
}
+ /**
+ * Sets the size of the button to be a custom value in pixels. If set to
+ * {@link #NO_CUSTOM_SIZE}, custom size will not be used and size will be calculated according
+ * to {@link #setSize(int)} method.
+ *
+ * @param size preferred size in pixels, or zero
+ *
+ * @attr ref android.support.design.R.styleable#FloatingActionButton_fabCustomSize
+ */
+ public void setCustomSize(int size) {
+ if (size < 0) {
+ throw new IllegalArgumentException("Custom size should be non-negative.");
+ }
+ mCustomSize = size;
+ }
+
+ /**
+ * Returns the custom size for this button.
+ *
+ * @return size in pixels, or {@link #NO_CUSTOM_SIZE}
+ */
+ public int getCustomSize() {
+ return mCustomSize;
+ }
+
int getSizeDimension() {
return getSizeDimension(mSize);
}
private int getSizeDimension(@Size final int size) {
final Resources res = getResources();
+ // If custom size is set, return it
+ if (mCustomSize != NO_CUSTOM_SIZE) {
+ return mCustomSize;
+ }
switch (size) {
case SIZE_AUTO:
// If we're set to auto, grab the size from resources and refresh
diff --git a/android/support/design/widget/TextInputEditText.java b/android/support/design/widget/TextInputEditText.java
index 7235ec22..ee6c32cd 100644
--- a/android/support/design/widget/TextInputEditText.java
+++ b/android/support/design/widget/TextInputEditText.java
@@ -18,6 +18,7 @@ package android.support.design.widget;
import android.content.Context;
import android.support.v7.widget.AppCompatEditText;
+import android.support.v7.widget.WithHint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewParent;
@@ -48,12 +49,12 @@ public class TextInputEditText extends AppCompatEditText {
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
final InputConnection ic = super.onCreateInputConnection(outAttrs);
if (ic != null && outAttrs.hintText == null) {
- // If we don't have a hint and our parent is a TextInputLayout, use it's hint for the
+ // If we don't have a hint and our parent implements WithHint, use its hint for the
// EditorInfo. This allows us to display a hint in 'extract mode'.
ViewParent parent = getParent();
while (parent instanceof View) {
- if (parent instanceof TextInputLayout) {
- outAttrs.hintText = ((TextInputLayout) parent).getHint();
+ if (parent instanceof WithHint) {
+ outAttrs.hintText = ((WithHint) parent).getHint();
break;
}
parent = parent.getParent();
diff --git a/android/support/design/widget/TextInputLayout.java b/android/support/design/widget/TextInputLayout.java
index c9e8010d..0540678e 100644
--- a/android/support/design/widget/TextInputLayout.java
+++ b/android/support/design/widget/TextInputLayout.java
@@ -49,10 +49,12 @@ import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.Space;
import android.support.v4.widget.TextViewCompat;
+import android.support.v4.widget.ViewGroupUtils;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.AppCompatDrawableManager;
import android.support.v7.widget.AppCompatTextView;
import android.support.v7.widget.TintTypedArray;
+import android.support.v7.widget.WithHint;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -113,7 +115,7 @@ import android.widget.TextView;
* may not return the TextInputLayout itself, but rather an intermediate View. If you need
* to access a View directly, set an {@code android:id} and use {@link View#findViewById(int)}.
*/
-public class TextInputLayout extends LinearLayout {
+public class TextInputLayout extends LinearLayout implements WithHint {
private static final int ANIMATION_DURATION = 200;
private static final int INVALID_MAX_LENGTH = -1;
@@ -497,6 +499,7 @@ public class TextInputLayout extends LinearLayout {
*
* @attr ref android.support.design.R.styleable#TextInputLayout_android_hint
*/
+ @Override
@Nullable
public CharSequence getHint() {
return mHintEnabled ? mHint : null;
diff --git a/android/support/doclava/DoclavaJavadocOptionFileOption.java b/android/support/doclava/DoclavaJavadocOptionFileOption.java
deleted file mode 100644
index db3f3188..00000000
--- a/android/support/doclava/DoclavaJavadocOptionFileOption.java
+++ /dev/null
@@ -1,75 +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.doclava;
-
-import org.gradle.external.javadoc.internal.AbstractJavadocOptionFileOption;
-import org.gradle.external.javadoc.internal.JavadocOptionFileWriterContext;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Iterator;
-
-/**
- * This class is used to hold complex argument(s) to doclava
- */
-public class DoclavaJavadocOptionFileOption extends
- AbstractJavadocOptionFileOption<Iterable<String>> {
-
- public DoclavaJavadocOptionFileOption(String option) {
- super(option, null);
- }
-
- public DoclavaJavadocOptionFileOption(String option, Iterable<String> value) {
- super(option, value);
- }
-
- @Override
- public void write(JavadocOptionFileWriterContext writerContext) throws IOException {
- writerContext.writeOptionHeader(getOption());
-
- final Iterable<String> args = getValue();
- if (args != null) {
- final Iterator<String> iter = args.iterator();
- while (true) {
- writerContext.writeValue(iter.next());
- if (!iter.hasNext()) {
- break;
- }
- writerContext.write(" ");
- }
- }
-
- writerContext.newLine();
- }
-
- /**
- * @return a deep copy of the option
- */
- public DoclavaJavadocOptionFileOption duplicate() {
- final Iterable<String> value = getValue();
- final ArrayList<String> valueCopy;
- if (value != null) {
- valueCopy = new ArrayList<>();
- for (String item : value) {
- valueCopy.add(item);
- }
- } else {
- valueCopy = null;
- }
- return new DoclavaJavadocOptionFileOption(getOption(), valueCopy);
- }
-}
diff --git a/android/support/graphics/drawable/VectorDrawableCompat.java b/android/support/graphics/drawable/VectorDrawableCompat.java
index 2c7ae41c..a34fe2b8 100644
--- a/android/support/graphics/drawable/VectorDrawableCompat.java
+++ b/android/support/graphics/drawable/VectorDrawableCompat.java
@@ -173,6 +173,10 @@ import java.util.Stack;
* <dd>Sets the lineJoin for a stroked path: miter,round,bevel. Default is miter.</dd>
* <dt><code>android:strokeMiterLimit</code></dt>
* <dd>Sets the Miter limit for a stroked path. Default is 4.</dd>
+ * <dt><code>android:fillType</code></dt>
+ * <dd>Sets the fillType for a path. The types can be either "evenOdd" or "nonZero". They behave the
+ * same as SVG's "fill-rule" properties. Default is nonZero. For more details, see
+ * <a href="https://www.w3.org/TR/SVG/painting.html#FillRuleProperty">FillRuleProperty</a></dd>
* </dl></dd>
* </dl>
*
diff --git a/android/support/mediacompat/testlib/MediaBrowserConstants.java b/android/support/mediacompat/testlib/MediaBrowserConstants.java
index 86024d90..f961308d 100644
--- a/android/support/mediacompat/testlib/MediaBrowserConstants.java
+++ b/android/support/mediacompat/testlib/MediaBrowserConstants.java
@@ -31,14 +31,16 @@ public class MediaBrowserConstants {
public static final int SET_SESSION_TOKEN = 7;
public static final String MEDIA_ID_ROOT = "test_media_id_root";
-
- public static final String EXTRAS_KEY = "test_extras_key";
- public static final String EXTRAS_VALUE = "test_extras_value";
-
public static final String MEDIA_ID_INVALID = "test_media_id_invalid";
public static final String MEDIA_ID_CHILDREN_DELAYED = "test_media_id_children_delayed";
public static final String MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED =
"test_media_id_on_load_item_not_implemented";
+ public static final String MEDIA_ID_INCLUDE_METADATA = "test_media_id_include_metadata";
+
+ public static final String EXTRAS_KEY = "test_extras_key";
+ public static final String EXTRAS_VALUE = "test_extras_value";
+
+ public static final String MEDIA_METADATA = "test_media_metadata";
public static final String SEARCH_QUERY = "children_2";
public static final String SEARCH_QUERY_FOR_NO_RESULT = "query no result";
diff --git a/android/support/mediacompat/testlib/MediaControllerConstants.java b/android/support/mediacompat/testlib/MediaControllerConstants.java
index 5fa086b3..49788882 100644
--- a/android/support/mediacompat/testlib/MediaControllerConstants.java
+++ b/android/support/mediacompat/testlib/MediaControllerConstants.java
@@ -26,6 +26,8 @@ public class MediaControllerConstants {
public static final int ADD_QUEUE_ITEM = 202;
public static final int ADD_QUEUE_ITEM_WITH_INDEX = 203;
public static final int REMOVE_QUEUE_ITEM = 204;
+ public static final int SET_VOLUME_TO = 205;
+ public static final int ADJUST_VOLUME = 206;
// TransportControls methods.
public static final int PLAY = 301;
diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java
index cbdccc1b..c0a64d4d 100644
--- a/android/support/mediacompat/testlib/MediaSessionConstants.java
+++ b/android/support/mediacompat/testlib/MediaSessionConstants.java
@@ -51,6 +51,8 @@ public class MediaSessionConstants {
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 String TEST_MEDIA_TITLE_1 = "media_title_1";
+ public static final String TEST_MEDIA_TITLE_2 = "media_title_2";
public static final long TEST_ACTION = 55L;
public static final int TEST_ERROR_CODE = 0x3;
diff --git a/android/support/mediacompat/testlib/VersionConstants.java b/android/support/mediacompat/testlib/VersionConstants.java
index 6533ee17..4b217b10 100644
--- a/android/support/mediacompat/testlib/VersionConstants.java
+++ b/android/support/mediacompat/testlib/VersionConstants.java
@@ -22,4 +22,7 @@ package android.support.mediacompat.testlib;
public class VersionConstants {
public static final String KEY_CLIENT_VERSION = "client_version";
public static final String KEY_SERVICE_VERSION = "service_version";
+
+ public static final String VERSION_TOT = "tot";
+ public static final String VERSION_PREVIOUS = "previous";
}
diff --git a/android/support/mediacompat/testlib/util/IntentUtil.java b/android/support/mediacompat/testlib/util/IntentUtil.java
index bbf97524..8d58a6ff 100644
--- a/android/support/mediacompat/testlib/util/IntentUtil.java
+++ b/android/support/mediacompat/testlib/util/IntentUtil.java
@@ -30,12 +30,13 @@ import java.util.ArrayList;
*/
public class IntentUtil {
+ public static final String SERVICE_PACKAGE_NAME = "android.support.mediacompat.service.test";
+ public static final String CLIENT_PACKAGE_NAME = "android.support.mediacompat.client.test";
+
public static final ComponentName SERVICE_RECEIVER_COMPONENT_NAME = new ComponentName(
- "android.support.mediacompat.service.test",
- "android.support.mediacompat.service.ServiceBroadcastReceiver");
+ SERVICE_PACKAGE_NAME, "android.support.mediacompat.service.ServiceBroadcastReceiver");
public static final ComponentName CLIENT_RECEIVER_COMPONENT_NAME = new ComponentName(
- "android.support.mediacompat.client.test",
- "android.support.mediacompat.client.ClientBroadcastReceiver");
+ CLIENT_PACKAGE_NAME, "android.support.mediacompat.client.ClientBroadcastReceiver");
public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
"android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
diff --git a/android/support/text/emoji/EmojiCompat.java b/android/support/text/emoji/EmojiCompat.java
index f258c12d..5436aa20 100644
--- a/android/support/text/emoji/EmojiCompat.java
+++ b/android/support/text/emoji/EmojiCompat.java
@@ -221,6 +221,7 @@ public class EmojiCompat {
*
* @see EmojiCompat.Config
*/
+ @SuppressWarnings("GuardedBy")
public static EmojiCompat init(@NonNull final Config config) {
if (sInstance == null) {
synchronized (sInstanceLock) {
@@ -238,6 +239,7 @@ public class EmojiCompat {
*
* @hide
*/
+ @SuppressWarnings("GuardedBy")
@RestrictTo(LIBRARY_GROUP)
@VisibleForTesting
public static EmojiCompat reset(@NonNull final Config config) {
@@ -252,6 +254,7 @@ public class EmojiCompat {
*
* @hide
*/
+ @SuppressWarnings("GuardedBy")
@RestrictTo(LIBRARY_GROUP)
@VisibleForTesting
public static EmojiCompat reset(final EmojiCompat emojiCompat) {
diff --git a/android/support/text/emoji/MetadataListReader.java b/android/support/text/emoji/MetadataListReader.java
index 6034726d..1008c171 100644
--- a/android/support/text/emoji/MetadataListReader.java
+++ b/android/support/text/emoji/MetadataListReader.java
@@ -275,7 +275,7 @@ class MetadataListReader {
@Override
public void skip(int numOfBytes) throws IOException {
while (numOfBytes > 0) {
- long skipped = mInputStream.skip(numOfBytes);
+ int skipped = (int) mInputStream.skip(numOfBytes);
if (skipped < 1) {
throw new IOException("Skip didn't move at least 1 byte forward");
}
diff --git a/android/support/text/emoji/widget/EmojiEditableFactory.java b/android/support/text/emoji/widget/EmojiEditableFactory.java
index 9793c9da..20cde4f9 100644
--- a/android/support/text/emoji/widget/EmojiEditableFactory.java
+++ b/android/support/text/emoji/widget/EmojiEditableFactory.java
@@ -55,6 +55,7 @@ final class EmojiEditableFactory extends Editable.Factory {
}
}
+ @SuppressWarnings("GuardedBy")
public static Editable.Factory getInstance() {
if (sInstance == null) {
synchronized (sInstanceLock) {
diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java
index d7020e91..9d159eca 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -2726,40 +2726,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
}
- // Observer is registered on Adapter to invalidate saved instance state
- final RecyclerView.AdapterDataObserver mObServer = new RecyclerView.AdapterDataObserver() {
- @Override
- public void onChanged() {
- mChildrenStates.clear();
- }
-
- @Override
- public void onItemRangeChanged(int positionStart, int itemCount) {
- if (DEBUG) {
- Log.v(getTag(), "onItemRangeChanged positionStart "
- + positionStart + " itemCount " + itemCount);
- }
- for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
- mChildrenStates.remove(i);
- }
- }
-
- @Override
- public void onItemRangeInserted(int positionStart, int itemCount) {
- mChildrenStates.clear();
- }
-
- @Override
- public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
- mChildrenStates.clear();
- }
-
- @Override
- public void onItemRangeRemoved(int positionStart, int itemCount) {
- mChildrenStates.clear();
- }
- };
-
@Override
public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
@@ -2771,12 +2737,14 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPositionOffset += itemCount;
}
}
+ mChildrenStates.clear();
}
@Override
public void onItemsChanged(RecyclerView recyclerView) {
if (DEBUG) Log.v(getTag(), "onItemsChanged");
mFocusPositionOffset = 0;
+ mChildrenStates.clear();
}
@Override
@@ -2797,6 +2765,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
}
}
+ mChildrenStates.clear();
}
@Override
@@ -2817,6 +2786,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPositionOffset += itemCount;
}
}
+ mChildrenStates.clear();
+ }
+
+ @Override
+ public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
+ if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart "
+ + positionStart + " itemCount " + itemCount);
+ for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
+ mChildrenStates.remove(i);
+ }
}
@Override
@@ -3515,16 +3494,12 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPosition = NO_POSITION;
mFocusPositionOffset = 0;
mChildrenStates.clear();
- oldAdapter.unregisterAdapterDataObserver(mObServer);
}
if (newAdapter instanceof FacetProviderAdapter) {
mFacetProviderAdapter = (FacetProviderAdapter) newAdapter;
} else {
mFacetProviderAdapter = null;
}
- if (newAdapter != null) {
- newAdapter.registerAdapterDataObserver(mObServer);
- }
super.onAdapterChanged(oldAdapter, newAdapter);
}
diff --git a/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
index 00e31a1a..f23ac0d4 100644
--- a/android/support/v4/graphics/TypefaceCompatApi26Impl.java
+++ b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
@@ -189,10 +189,9 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
/**
* Call FontFamily#abortCreation()
*/
- private boolean abortCreation(Object family) {
+ private void abortCreation(Object family) {
try {
- Boolean result = (Boolean) mAbortCreation.invoke(family);
- return result.booleanValue();
+ mAbortCreation.invoke(family);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
diff --git a/android/support/v4/media/MediaBrowserCompat.java b/android/support/v4/media/MediaBrowserCompat.java
index 7adf7d78..1b279253 100644
--- a/android/support/v4/media/MediaBrowserCompat.java
+++ b/android/support/v4/media/MediaBrowserCompat.java
@@ -41,10 +41,12 @@ import static android.support.v4.media.MediaBrowserProtocol.DATA_SEARCH_EXTRAS;
import static android.support.v4.media.MediaBrowserProtocol.DATA_SEARCH_QUERY;
import static android.support.v4.media.MediaBrowserProtocol.EXTRA_CLIENT_VERSION;
import static android.support.v4.media.MediaBrowserProtocol.EXTRA_MESSENGER_BINDER;
+import static android.support.v4.media.MediaBrowserProtocol.EXTRA_SERVICE_VERSION;
import static android.support.v4.media.MediaBrowserProtocol.EXTRA_SESSION_BINDER;
import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT;
import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT_FAILED;
import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_LOAD_CHILDREN;
+import static android.support.v4.media.MediaBrowserProtocol.SERVICE_VERSION_2;
import android.content.ComponentName;
import android.content.Context;
@@ -1581,6 +1583,7 @@ public final class MediaBrowserCompat {
protected final CallbackHandler mHandler = new CallbackHandler(this);
private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>();
+ protected int mServiceVersion;
protected ServiceBinderWrapper mServiceBinderWrapper;
protected Messenger mCallbacksMessenger;
private MediaSessionCompat.Token mMediaSessionToken;
@@ -1850,6 +1853,7 @@ public final class MediaBrowserCompat {
if (extras == null) {
return;
}
+ mServiceVersion = extras.getInt(EXTRA_SERVICE_VERSION, 0);
IBinder serviceBinder = BundleCompat.getBinder(extras, EXTRA_MESSENGER_BINDER);
if (serviceBinder != null) {
mServiceBinderWrapper = new ServiceBinderWrapper(serviceBinder, mRootHints);
@@ -1956,7 +1960,9 @@ public final class MediaBrowserCompat {
@Override
public void subscribe(@NonNull String parentId, @Nullable Bundle options,
@NonNull SubscriptionCallback callback) {
- if (mServiceBinderWrapper == null) {
+ // From service v2, we use compat code when subscribing.
+ // This is to prevent ClassNotFoundException when options has Parcelable in it.
+ if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) {
if (options == null) {
MediaBrowserCompatApi21.subscribe(
mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
@@ -1971,7 +1977,9 @@ public final class MediaBrowserCompat {
@Override
public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
- if (mServiceBinderWrapper == null) {
+ // From service v2, we use compat code when subscribing.
+ // This is to prevent ClassNotFoundException when options has Parcelable in it.
+ if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) {
if (callback == null) {
MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
} else {
diff --git a/android/support/v4/media/MediaBrowserProtocol.java b/android/support/v4/media/MediaBrowserProtocol.java
index 7c23d261..8ed152df 100644
--- a/android/support/v4/media/MediaBrowserProtocol.java
+++ b/android/support/v4/media/MediaBrowserProtocol.java
@@ -45,7 +45,15 @@ class MediaBrowserProtocol {
* MediaBrowserServiceCompat.
*/
public static final int SERVICE_VERSION_1 = 1;
- public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_1;
+
+ /**
+ * To prevent ClassNotFoundException from Parcelables, MediaBrowser(Service)Compat tries to
+ * avoid using framework code as much as possible (b/62648808). For backward compatibility,
+ * service v2 is introduced so that the browser can distinguish whether the service supports
+ * subscribing through compat code.
+ */
+ public static final int SERVICE_VERSION_2 = 2;
+ public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_2;
/*
* Messages sent from the media browser service compat to the media browser compat.
diff --git a/android/support/v4/media/MediaBrowserServiceCompat.java b/android/support/v4/media/MediaBrowserServiceCompat.java
index debc66e8..27bf0e30 100644
--- a/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -278,28 +278,8 @@ public abstract class MediaBrowserServiceCompat extends Service {
@Override
public void notifyChildrenChanged(final String parentId, final Bundle options) {
- if (mMessenger == null) {
- MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
- } else {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- for (IBinder binder : mConnections.keySet()) {
- ConnectionRecord connection = mConnections.get(binder);
- List<Pair<IBinder, Bundle>> callbackList =
- connection.subscriptions.get(parentId);
- if (callbackList != null) {
- for (Pair<IBinder, Bundle> callback : callbackList) {
- if (MediaBrowserCompatUtils.hasDuplicatedItems(
- options, callback.second)) {
- performLoadChildren(parentId, connection, callback.second);
- }
- }
- }
- }
- }
- });
- }
+ notifyChildrenChangedForFramework(parentId, options);
+ notifyChildrenChangedForCompat(parentId, options);
}
@Override
@@ -373,6 +353,31 @@ public abstract class MediaBrowserServiceCompat extends Service {
};
MediaBrowserServiceCompat.this.onLoadChildren(parentId, result);
}
+
+ void notifyChildrenChangedForFramework(final String parentId, final Bundle options) {
+ MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
+ }
+
+ void notifyChildrenChangedForCompat(final String parentId, final Bundle options) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ for (IBinder binder : mConnections.keySet()) {
+ ConnectionRecord connection = mConnections.get(binder);
+ List<Pair<IBinder, Bundle>> callbackList =
+ connection.subscriptions.get(parentId);
+ if (callbackList != null) {
+ for (Pair<IBinder, Bundle> callback : callbackList) {
+ if (MediaBrowserCompatUtils.hasDuplicatedItems(
+ options, callback.second)) {
+ performLoadChildren(parentId, connection, callback.second);
+ }
+ }
+ }
+ }
+ }
+ });
+ }
}
@RequiresApi(23)
@@ -421,20 +426,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
}
@Override
- public void notifyChildrenChanged(final String parentId, final Bundle options) {
- if (mMessenger == null) {
- if (options == null) {
- MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
- } else {
- MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
- options);
- }
- } else {
- super.notifyChildrenChanged(parentId, options);
- }
- }
-
- @Override
public void onLoadChildren(String parentId,
final MediaBrowserServiceCompatApi26.ResultWrapper resultWrapper, Bundle options) {
final Result<List<MediaBrowserCompat.MediaItem>> result
@@ -470,6 +461,16 @@ public abstract class MediaBrowserServiceCompat extends Service {
}
return MediaBrowserServiceCompatApi26.getBrowserRootHints(mServiceObj);
}
+
+ @Override
+ void notifyChildrenChangedForFramework(final String parentId, final Bundle options) {
+ if (options != null) {
+ MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
+ options);
+ } else {
+ super.notifyChildrenChangedForFramework(parentId, options);
+ }
+ }
}
private final class ServiceHandler extends Handler {
diff --git a/android/support/v4/view/NestedScrollingParent2.java b/android/support/v4/view/NestedScrollingParent2.java
index db41c461..2ab463ec 100644
--- a/android/support/v4/view/NestedScrollingParent2.java
+++ b/android/support/v4/view/NestedScrollingParent2.java
@@ -18,7 +18,6 @@
package android.support.v4.view;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat.NestedScrollType;
import android.support.v4.view.ViewCompat.ScrollAxis;
import android.view.MotionEvent;
@@ -144,7 +143,7 @@ public interface NestedScrollingParent2 extends NestedScrollingParent {
* @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
* @param type the type of input which cause this scroll event
*/
- void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed,
+ void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
@NestedScrollType int type);
}
diff --git a/android/support/v4/view/PagerAdapter.java b/android/support/v4/view/PagerAdapter.java
index a8fb099c..af8e076f 100644
--- a/android/support/v4/view/PagerAdapter.java
+++ b/android/support/v4/view/PagerAdapter.java
@@ -131,6 +131,7 @@ public abstract class PagerAdapter {
/**
* Called to inform the adapter of which item is currently considered to
* be the "primary", that is the one show to the user as the current page.
+ * This method will not be invoked when the adapter contains no items.
*
* @param container The containing View from which the page will be removed.
* @param position The page position that is now the primary.
diff --git a/android/support/v4/view/ViewPager.java b/android/support/v4/view/ViewPager.java
index 36d8696c..350fe955 100644
--- a/android/support/v4/view/ViewPager.java
+++ b/android/support/v4/view/ViewPager.java
@@ -1224,6 +1224,8 @@ public class ViewPager extends ViewGroup {
}
calculatePageOffsets(curItem, curIndex, oldCurInfo);
+
+ mAdapter.setPrimaryItem(this, mCurItem, curItem.object);
}
if (DEBUG) {
@@ -1233,8 +1235,6 @@ public class ViewPager extends ViewGroup {
}
}
- mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
-
mAdapter.finishUpdate(this);
// Check width measurement of current pages and drawing sort order.
diff --git a/android/support/design/widget/DirectedAcyclicGraph.java b/android/support/v4/widget/DirectedAcyclicGraph.java
index 85a32cd5..83c62c06 100644
--- a/android/support/design/widget/DirectedAcyclicGraph.java
+++ b/android/support/v4/widget/DirectedAcyclicGraph.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,13 @@
* limitations under the License.
*/
-package android.support.design.widget;
+package android.support.v4.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
import android.support.v4.util.Pools;
import android.support.v4.util.SimpleArrayMap;
@@ -27,8 +30,13 @@ import java.util.List;
/**
* A class which represents a simple directed acyclic graph.
+ *
+ * @param <T> Class for the data objects of this graph.
+ *
+ * @hide
*/
-final class DirectedAcyclicGraph<T> {
+@RestrictTo(LIBRARY)
+public final class DirectedAcyclicGraph<T> {
private final Pools.Pool<ArrayList<T>> mListPool = new Pools.SimplePool<>(10);
private final SimpleArrayMap<T, ArrayList<T>> mGraph = new SimpleArrayMap<>();
@@ -42,7 +50,7 @@ final class DirectedAcyclicGraph<T> {
*
* @param node the node to add
*/
- void addNode(@NonNull T node) {
+ public void addNode(@NonNull T node) {
if (!mGraph.containsKey(node)) {
mGraph.put(node, null);
}
@@ -51,7 +59,7 @@ final class DirectedAcyclicGraph<T> {
/**
* Returns true if the node is already present in the graph, false otherwise.
*/
- boolean contains(@NonNull T node) {
+ public boolean contains(@NonNull T node) {
return mGraph.containsKey(node);
}
@@ -64,7 +72,7 @@ final class DirectedAcyclicGraph<T> {
* @param node the parent node
* @param incomingEdge the node which has is an incoming edge to {@code node}
*/
- void addEdge(@NonNull T node, @NonNull T incomingEdge) {
+ public void addEdge(@NonNull T node, @NonNull T incomingEdge) {
if (!mGraph.containsKey(node) || !mGraph.containsKey(incomingEdge)) {
throw new IllegalArgumentException("All nodes must be present in the graph before"
+ " being added as an edge");
@@ -86,7 +94,7 @@ final class DirectedAcyclicGraph<T> {
* @return a list containing any incoming edges, or null if there are none.
*/
@Nullable
- List getIncomingEdges(@NonNull T node) {
+ public List getIncomingEdges(@NonNull T node) {
return mGraph.get(node);
}
@@ -97,7 +105,7 @@ final class DirectedAcyclicGraph<T> {
* @return a list containing any outgoing edges, or null if there are none.
*/
@Nullable
- List<T> getOutgoingEdges(@NonNull T node) {
+ public List<T> getOutgoingEdges(@NonNull T node) {
ArrayList<T> result = null;
for (int i = 0, size = mGraph.size(); i < size; i++) {
ArrayList<T> edges = mGraph.valueAt(i);
@@ -111,7 +119,14 @@ final class DirectedAcyclicGraph<T> {
return result;
}
- boolean hasOutgoingEdges(@NonNull T node) {
+ /**
+ * Checks whether we have any outgoing edges for the given node (i.e. nodes which have
+ * an incoming edge from the given node).
+ *
+ * @return <code>true</code> if the node has any outgoing edges, <code>false</code>
+ * otherwise.
+ */
+ public boolean hasOutgoingEdges(@NonNull T node) {
for (int i = 0, size = mGraph.size(); i < size; i++) {
ArrayList<T> edges = mGraph.valueAt(i);
if (edges != null && edges.contains(node)) {
@@ -124,7 +139,7 @@ final class DirectedAcyclicGraph<T> {
/**
* Clears the internal graph, and releases resources to pools.
*/
- void clear() {
+ public void clear() {
for (int i = 0, size = mGraph.size(); i < size; i++) {
ArrayList<T> edges = mGraph.valueAt(i);
if (edges != null) {
@@ -143,7 +158,7 @@ final class DirectedAcyclicGraph<T> {
* of the graph. The node at the end of the list will have no dependencies on other nodes.</p>
*/
@NonNull
- ArrayList<T> getSortedList() {
+ public ArrayList<T> getSortedList() {
mSortResult.clear();
mSortTmpMarked.clear();
@@ -198,4 +213,4 @@ final class DirectedAcyclicGraph<T> {
list.clear();
mListPool.release(list);
}
-} \ No newline at end of file
+}
diff --git a/android/support/design/widget/ViewGroupUtils.java b/android/support/v4/widget/ViewGroupUtils.java
index 5d8b5c74..986b4c20 100644
--- a/android/support/design/widget/ViewGroupUtils.java
+++ b/android/support/v4/widget/ViewGroupUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,23 @@
* limitations under the License.
*/
-package android.support.design.widget;
+package android.support.v4.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.support.annotation.RestrictTo;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
-class ViewGroupUtils {
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class ViewGroupUtils {
private static final ThreadLocal<Matrix> sMatrix = new ThreadLocal<>();
private static final ThreadLocal<RectF> sRectF = new ThreadLocal<>();
@@ -65,7 +72,7 @@ class ViewGroupUtils {
* @param descendant descendant view to reference
* @param out rect to set to the bounds of the descendant view
*/
- static void getDescendantRect(ViewGroup parent, View descendant, Rect out) {
+ public static void getDescendantRect(ViewGroup parent, View descendant, Rect out) {
out.set(0, 0, descendant.getWidth(), descendant.getHeight());
offsetDescendantRect(parent, descendant, out);
}
diff --git a/android/support/v7/preference/CollapsiblePreferenceGroupController.java b/android/support/v7/preference/CollapsiblePreferenceGroupController.java
index e15ca18f..b63ff75b 100644
--- a/android/support/v7/preference/CollapsiblePreferenceGroupController.java
+++ b/android/support/v7/preference/CollapsiblePreferenceGroupController.java
@@ -166,7 +166,7 @@ final class CollapsiblePreferenceGroupController
CharSequence summary = null;
for (int i = collapsedIndex; i < flattenedPreferenceList.size(); i++) {
final Preference preference = flattenedPreferenceList.get(i);
- if (preference instanceof PreferenceGroup) {
+ if (preference instanceof PreferenceGroup || !preference.isVisible()) {
continue;
}
final CharSequence title = preference.getTitle();
diff --git a/android/support/v7/util/SortedList.java b/android/support/v7/util/SortedList.java
index af000a1e..bd07b01e 100644
--- a/android/support/v7/util/SortedList.java
+++ b/android/support/v7/util/SortedList.java
@@ -16,6 +16,7 @@
package android.support.v7.util;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.lang.reflect.Array;
@@ -51,17 +52,23 @@ public class SortedList<T> {
T[] mData;
/**
- * A copy of the previous list contents used during the merge phase of addAll.
+ * A reference to the previous set of data that is kept during a mutation operation (addAll or
+ * replaceAll).
*/
private T[] mOldData;
+
+ /**
+ * The current index into mOldData that has not yet been processed during a mutation operation
+ * (addAll or replaceAll).
+ */
private int mOldDataStart;
private int mOldDataSize;
/**
- * The size of the valid portion of mData during the merge phase of addAll.
+ * The current index into the new data that has not yet been processed during a mutation
+ * operation (addAll or replaceAll).
*/
- private int mMergedSize;
-
+ private int mNewDataStart;
/**
* The callback instance that controls the behavior of the SortedList and get notified when
@@ -133,7 +140,7 @@ public class SortedList<T> {
* @see Callback#areContentsTheSame(Object, Object)}
*/
public int add(T item) {
- throwIfMerging();
+ throwIfInMutationOperation();
return add(item, true);
}
@@ -142,30 +149,30 @@ public class SortedList<T> {
* except the callback events may be in a different order/granularity since addAll can batch
* them for better performance.
* <p>
- * If allowed, may modify the input array and even take the ownership over it in order
- * to avoid extra memory allocation during sorting and deduplication.
- * </p>
+ * If allowed, will reference the input array during, and possibly after, the operation to avoid
+ * extra memory allocation, in which case you should not continue to reference or modify the
+ * array yourself.
+ * <p>
* @param items Array of items to be added into the list.
- * @param mayModifyInput If true, SortedList is allowed to modify the input.
- * @see SortedList#addAll(Object[] items)
+ * @param mayModifyInput If true, SortedList is allowed to modify and permanently reference the
+ * input array.
+ * @see SortedList#addAll(T[] items)
*/
public void addAll(T[] items, boolean mayModifyInput) {
- throwIfMerging();
+ throwIfInMutationOperation();
if (items.length == 0) {
return;
}
+
if (mayModifyInput) {
addAllInternal(items);
} else {
- T[] copy = (T[]) Array.newInstance(mTClass, items.length);
- System.arraycopy(items, 0, copy, 0, items.length);
- addAllInternal(copy);
+ addAllInternal(copyArray(items));
}
-
}
/**
- * Adds the given items to the list. Does not modify the input.
+ * Adds the given items to the list. Does not modify or retain the input.
*
* @see SortedList#addAll(T[] items, boolean mayModifyInput)
*
@@ -176,7 +183,7 @@ public class SortedList<T> {
}
/**
- * Adds the given items to the list. Does not modify the input.
+ * Adds the given items to the list. Does not modify or retain the input.
*
* @see SortedList#addAll(T[] items, boolean mayModifyInput)
*
@@ -187,27 +194,134 @@ public class SortedList<T> {
addAll(items.toArray(copy), true);
}
- private void addAllInternal(T[] newItems) {
- final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback);
- if (forceBatchedUpdates) {
- beginBatchedUpdates();
+ /**
+ * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events
+ * for each change detected as appropriate.
+ * <p>
+ * If allowed, will reference the input array during, and possibly after, the operation to avoid
+ * extra memory allocation, in which case you should not continue to reference or modify the
+ * array yourself.
+ * <p>
+ * Note: this method does not detect moves or dispatch
+ * {@link ListUpdateCallback#onMoved(int, int)} events. It instead treats moves as a remove
+ * followed by an add and therefore dispatches {@link ListUpdateCallback#onRemoved(int, int)}
+ * and {@link ListUpdateCallback#onRemoved(int, int)} events. See {@link DiffUtil} if you want
+ * your implementation to dispatch move events.
+ * <p>
+ * @param items Array of items to replace current items.
+ * @param mayModifyInput If true, SortedList is allowed to modify and permanently reference the
+ * input array.
+ * @see #replaceAll(T[])
+ */
+ public void replaceAll(@NonNull T[] items, boolean mayModifyInput) {
+ throwIfInMutationOperation();
+
+ if (mayModifyInput) {
+ replaceAllInternal(items);
+ } else {
+ replaceAllInternal(copyArray(items));
}
+ }
- mOldData = mData;
- mOldDataStart = 0;
- mOldDataSize = mSize;
+ /**
+ * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events
+ * for each change detected as appropriate. Does not modify or retain the input.
+ *
+ * @see #replaceAll(T[], boolean)
+ *
+ * @param items Array of items to replace current items.
+ */
+ public void replaceAll(@NonNull T... items) {
+ replaceAll(items, false);
+ }
- Arrays.sort(newItems, mCallback); // Arrays.sort is stable.
+ /**
+ * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events
+ * for each change detected as appropriate. Does not modify or retain the input.
+ *
+ * @see #replaceAll(T[], boolean)
+ *
+ * @param items Array of items to replace current items.
+ */
+ public void replaceAll(@NonNull Collection<T> items) {
+ T[] copy = (T[]) Array.newInstance(mTClass, items.size());
+ replaceAll(items.toArray(copy), true);
+ }
+
+ private void addAllInternal(T[] newItems) {
+ if (newItems.length < 1) {
+ return;
+ }
+
+ final int newSize = sortAndDedup(newItems);
- final int newSize = deduplicate(newItems);
if (mSize == 0) {
mData = newItems;
mSize = newSize;
- mMergedSize = newSize;
mCallback.onInserted(0, newSize);
} else {
merge(newItems, newSize);
}
+ }
+
+ private void replaceAllInternal(@NonNull T[] newData) {
+ final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback);
+ if (forceBatchedUpdates) {
+ beginBatchedUpdates();
+ }
+
+ mOldDataStart = 0;
+ mOldDataSize = mSize;
+ mOldData = mData;
+
+ mNewDataStart = 0;
+ int newSize = sortAndDedup(newData);
+ mData = (T[]) Array.newInstance(mTClass, newSize);
+
+ while (mNewDataStart < newSize || mOldDataStart < mOldDataSize) {
+ if (mOldDataStart >= mOldDataSize) {
+ int insertIndex = mNewDataStart;
+ int itemCount = newSize - mNewDataStart;
+ System.arraycopy(newData, insertIndex, mData, insertIndex, itemCount);
+ mNewDataStart += itemCount;
+ mSize += itemCount;
+ mCallback.onInserted(insertIndex, itemCount);
+ break;
+ }
+ if (mNewDataStart >= newSize) {
+ int itemCount = mOldDataSize - mOldDataStart;
+ mSize -= itemCount;
+ mCallback.onRemoved(mNewDataStart, itemCount);
+ break;
+ }
+
+ T oldItem = mOldData[mOldDataStart];
+ T newItem = newData[mNewDataStart];
+
+ int result = mCallback.compare(oldItem, newItem);
+ if (result < 0) {
+ replaceAllRemove();
+ } else if (result > 0) {
+ replaceAllInsert(newItem);
+ } else {
+ if (!mCallback.areItemsTheSame(oldItem, newItem)) {
+ // The items aren't the same even though they were supposed to occupy the same
+ // place, so both notify to remove and add an item in the current location.
+ replaceAllRemove();
+ replaceAllInsert(newItem);
+ } else {
+ mData[mNewDataStart] = newItem;
+ mOldDataStart++;
+ mNewDataStart++;
+ if (!mCallback.areContentsTheSame(oldItem, newItem)) {
+ // The item is the same but the contents have changed, so notify that an
+ // onChanged event has occurred.
+ mCallback.onChanged(mNewDataStart - 1, 1,
+ mCallback.getChangePayload(oldItem, newItem));
+ }
+ }
+ }
+ }
mOldData = null;
@@ -216,17 +330,33 @@ public class SortedList<T> {
}
}
+ private void replaceAllInsert(T newItem) {
+ mData[mNewDataStart] = newItem;
+ mNewDataStart++;
+ mSize++;
+ mCallback.onInserted(mNewDataStart - 1, 1);
+ }
+
+ private void replaceAllRemove() {
+ mSize--;
+ mOldDataStart++;
+ mCallback.onRemoved(mNewDataStart, 1);
+ }
+
/**
- * Remove duplicate items, leaving only the last item from each group of "same" items.
- * Move the remaining items to the beginning of the array.
+ * Sorts and removes duplicate items, leaving only the last item from each group of "same"
+ * items. Move the remaining items to the beginning of the array.
*
* @return Number of deduplicated items at the beginning of the array.
*/
- private int deduplicate(T[] items) {
+ private int sortAndDedup(@NonNull T[] items) {
if (items.length == 0) {
- throw new IllegalArgumentException("Input array must be non-empty");
+ return 0;
}
+ // Arrays.sort is stable.
+ Arrays.sort(items, mCallback);
+
// Keep track of the range of equal items at the end of the output.
// Start with the range containing just the first item.
int rangeStart = 0;
@@ -236,9 +366,6 @@ public class SortedList<T> {
T currentItem = items[i];
int compare = mCallback.compare(items[rangeStart], currentItem);
- if (compare > 0) {
- throw new IllegalArgumentException("Input must be sorted in ascending order.");
- }
if (compare == 0) {
// The range of equal items continues, update it.
@@ -278,27 +405,36 @@ public class SortedList<T> {
* This method assumes that newItems are sorted and deduplicated.
*/
private void merge(T[] newData, int newDataSize) {
+ final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback);
+ if (forceBatchedUpdates) {
+ beginBatchedUpdates();
+ }
+
+ mOldData = mData;
+ mOldDataStart = 0;
+ mOldDataSize = mSize;
+
final int mergedCapacity = mSize + newDataSize + CAPACITY_GROWTH;
mData = (T[]) Array.newInstance(mTClass, mergedCapacity);
- mMergedSize = 0;
+ mNewDataStart = 0;
int newDataStart = 0;
while (mOldDataStart < mOldDataSize || newDataStart < newDataSize) {
if (mOldDataStart == mOldDataSize) {
// No more old items, copy the remaining new items.
int itemCount = newDataSize - newDataStart;
- System.arraycopy(newData, newDataStart, mData, mMergedSize, itemCount);
- mMergedSize += itemCount;
+ System.arraycopy(newData, newDataStart, mData, mNewDataStart, itemCount);
+ mNewDataStart += itemCount;
mSize += itemCount;
- mCallback.onInserted(mMergedSize - itemCount, itemCount);
+ mCallback.onInserted(mNewDataStart - itemCount, itemCount);
break;
}
if (newDataStart == newDataSize) {
// No more new items, copy the remaining old items.
int itemCount = mOldDataSize - mOldDataStart;
- System.arraycopy(mOldData, mOldDataStart, mData, mMergedSize, itemCount);
- mMergedSize += itemCount;
+ System.arraycopy(mOldData, mOldDataStart, mData, mNewDataStart, itemCount);
+ mNewDataStart += itemCount;
break;
}
@@ -307,36 +443,47 @@ public class SortedList<T> {
int compare = mCallback.compare(oldItem, newItem);
if (compare > 0) {
// New item is lower, output it.
- mData[mMergedSize++] = newItem;
+ mData[mNewDataStart++] = newItem;
mSize++;
newDataStart++;
- mCallback.onInserted(mMergedSize - 1, 1);
+ mCallback.onInserted(mNewDataStart - 1, 1);
} else if (compare == 0 && mCallback.areItemsTheSame(oldItem, newItem)) {
// Items are the same. Output the new item, but consume both.
- mData[mMergedSize++] = newItem;
+ mData[mNewDataStart++] = newItem;
newDataStart++;
mOldDataStart++;
if (!mCallback.areContentsTheSame(oldItem, newItem)) {
- mCallback.onChanged(mMergedSize - 1, 1,
+ mCallback.onChanged(mNewDataStart - 1, 1,
mCallback.getChangePayload(oldItem, newItem));
}
} else {
// Old item is lower than or equal to (but not the same as the new). Output it.
// New item with the same sort order will be inserted later.
- mData[mMergedSize++] = oldItem;
+ mData[mNewDataStart++] = oldItem;
mOldDataStart++;
}
}
+
+ mOldData = null;
+
+ if (forceBatchedUpdates) {
+ endBatchedUpdates();
+ }
}
- private void throwIfMerging() {
+ /**
+ * Throws an exception if called while we are in the middle of a mutation operation (addAll or
+ * replaceAll).
+ */
+ private void throwIfInMutationOperation() {
if (mOldData != null) {
- throw new IllegalStateException("Cannot call this method from within addAll");
+ throw new IllegalStateException("Data cannot be mutated in the middle of a batch "
+ + "update operation such as addAll or replaceAll.");
}
}
/**
- * Batches adapter updates that happen between calling this method until calling
+ * Batches adapter updates that happen after calling this method and before calling
* {@link #endBatchedUpdates()}. For example, if you add multiple items in a loop
* and they are placed into consecutive indices, SortedList calls
* {@link Callback#onInserted(int, int)} only once with the proper item count. If an event
@@ -368,7 +515,7 @@ public class SortedList<T> {
* has no effect.
*/
public void beginBatchedUpdates() {
- throwIfMerging();
+ throwIfInMutationOperation();
if (mCallback instanceof BatchedCallback) {
return;
}
@@ -382,7 +529,7 @@ public class SortedList<T> {
* Ends the update transaction and dispatches any remaining event to the callback.
*/
public void endBatchedUpdates() {
- throwIfMerging();
+ throwIfInMutationOperation();
if (mCallback instanceof BatchedCallback) {
((BatchedCallback) mCallback).dispatchLastEvent();
}
@@ -424,7 +571,7 @@ public class SortedList<T> {
* @return True if item is removed, false if item cannot be found in the list.
*/
public boolean remove(T item) {
- throwIfMerging();
+ throwIfInMutationOperation();
return remove(item, true);
}
@@ -436,7 +583,7 @@ public class SortedList<T> {
* @return The removed item.
*/
public T removeItemAt(int index) {
- throwIfMerging();
+ throwIfInMutationOperation();
T item = get(index);
removeItemAtIndex(index, true);
return item;
@@ -481,7 +628,7 @@ public class SortedList<T> {
* @see #add(Object)
*/
public void updateItemAt(int index, T item) {
- throwIfMerging();
+ throwIfInMutationOperation();
final T existing = get(index);
// assume changed if the same object is given back
boolean contentsChanged = existing == item || !mCallback.areContentsTheSame(existing, item);
@@ -535,7 +682,7 @@ public class SortedList<T> {
* @see #add(Object)
*/
public void recalculatePositionOfItemAt(int index) {
- throwIfMerging();
+ throwIfInMutationOperation();
// TODO can be improved
final T item = get(index);
removeItemAtIndex(index, false);
@@ -562,8 +709,8 @@ public class SortedList<T> {
if (mOldData != null) {
// The call is made from a callback during addAll execution. The data is split
// between mData and mOldData.
- if (index >= mMergedSize) {
- return mOldData[index - mMergedSize + mOldDataStart];
+ if (index >= mNewDataStart) {
+ return mOldData[index - mNewDataStart + mOldDataStart];
}
}
return mData[index];
@@ -579,13 +726,13 @@ public class SortedList<T> {
*/
public int indexOf(T item) {
if (mOldData != null) {
- int index = findIndexOf(item, mData, 0, mMergedSize, LOOKUP);
+ int index = findIndexOf(item, mData, 0, mNewDataStart, LOOKUP);
if (index != INVALID_POSITION) {
return index;
}
index = findIndexOf(item, mOldData, mOldDataStart, mOldDataSize, LOOKUP);
if (index != INVALID_POSITION) {
- return index - mOldDataStart + mMergedSize;
+ return index - mOldDataStart + mNewDataStart;
}
return INVALID_POSITION;
}
@@ -662,11 +809,17 @@ public class SortedList<T> {
mSize++;
}
+ private T[] copyArray(T[] items) {
+ T[] copy = (T[]) Array.newInstance(mTClass, items.length);
+ System.arraycopy(items, 0, copy, 0, items.length);
+ return copy;
+ }
+
/**
* Removes all items from the SortedList.
*/
public void clear() {
- throwIfMerging();
+ throwIfInMutationOperation();
if (mSize == 0) {
return;
}
@@ -722,8 +875,8 @@ public class SortedList<T> {
* so
* that you can change its behavior depending on your UI.
* <p>
- * For example, if you are using SortedList with a {@link android.support.v7.widget.RecyclerView.Adapter
- * RecyclerView.Adapter}, you should
+ * For example, if you are using SortedList with a
+ * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should
* return whether the items' visual representations are the same or not.
*
* @param oldItem The previous representation of the object.
@@ -734,7 +887,7 @@ public class SortedList<T> {
abstract public boolean areContentsTheSame(T2 oldItem, T2 newItem);
/**
- * Called by the SortedList to decide whether two object represent the same Item or not.
+ * Called by the SortedList to decide whether two objects represent the same Item or not.
* <p>
* For example, if your items have unique ids, this method should check their equality.
*
diff --git a/android/support/v7/util/SortedListTest.java b/android/support/v7/util/SortedListTest.java
index 47d2ac0f..f8bc496c 100644
--- a/android/support/v7/util/SortedListTest.java
+++ b/android/support/v7/util/SortedListTest.java
@@ -27,11 +27,15 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Queue;
import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
@RunWith(JUnit4.class)
@SmallTest
@@ -44,6 +48,8 @@ public class SortedListTest extends TestCase {
List<Pair> mUpdates = new ArrayList<Pair>();
private boolean mPayloadChanges = false;
List<PayloadChange> mPayloadUpdates = new ArrayList<>();
+ Queue<AssertListStateRunnable> mCallbackRunnables;
+ List<Event> mEvents = new ArrayList<>();
private SortedList.Callback<Item> mCallback;
InsertedCallback<Item> mInsertedCallback;
ChangedCallback<Item> mChangedCallback;
@@ -67,6 +73,7 @@ public class SortedListTest extends TestCase {
@Before
public void setUp() throws Exception {
super.setUp();
+
mCallback = new SortedList.Callback<Item>() {
@Override
public int compare(Item o1, Item o2) {
@@ -75,28 +82,35 @@ public class SortedListTest extends TestCase {
@Override
public void onInserted(int position, int count) {
+ mEvents.add(new Event(TYPE.ADD, position, count));
mAdditions.add(new Pair(position, count));
if (mInsertedCallback != null) {
mInsertedCallback.onInserted(position, count);
}
+ pollAndRun(mCallbackRunnables);
}
@Override
public void onRemoved(int position, int count) {
+ mEvents.add(new Event(TYPE.REMOVE, position, count));
mRemovals.add(new Pair(position, count));
+ pollAndRun(mCallbackRunnables);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
+ mEvents.add(new Event(TYPE.MOVE, fromPosition, toPosition));
mMoves.add(new Pair(fromPosition, toPosition));
}
@Override
public void onChanged(int position, int count) {
+ mEvents.add(new Event(TYPE.CHANGE, position, count));
mUpdates.add(new Pair(position, count));
if (mChangedCallback != null) {
mChangedCallback.onChanged(position, count);
}
+ pollAndRun(mCallbackRunnables);
}
@Override
@@ -110,7 +124,7 @@ public class SortedListTest extends TestCase {
@Override
public boolean areContentsTheSame(Item oldItem, Item newItem) {
- return oldItem.cmpField == newItem.cmpField && oldItem.data == newItem.data;
+ return oldItem.data == newItem.data;
}
@Override
@@ -127,11 +141,41 @@ public class SortedListTest extends TestCase {
return null;
}
};
- mInsertedCallback = null;
- mChangedCallback = null;
mList = new SortedList<Item>(Item.class, mCallback);
}
+ private void pollAndRun(Queue<AssertListStateRunnable> queue) {
+ if (queue != null) {
+ Runnable runnable = queue.poll();
+ assertNotNull(runnable);
+ runnable.run();
+ }
+ }
+
+ @Test
+ public void testValidMethodsDuringOnInsertedCallbackFromEmptyList() {
+
+ final Item[] items =
+ new Item[] {new Item(0), new Item(1), new Item(2)};
+
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ mInsertedCallback = new InsertedCallback<Item>() {
+ @Override
+ public void onInserted(int position, int count) {
+ for (int i = 0; i < count; i++) {
+ assertEquals(mList.get(i), items[i]);
+ assertEquals(mList.indexOf(items[i]), i);
+ atomicInteger.incrementAndGet();
+ }
+ }
+ };
+
+ mList.add(items[0]);
+ mList.clear();
+ mList.addAll(items, false);
+ assertEquals(4, atomicInteger.get());
+ }
+
@Test
public void testEmpty() {
assertEquals("empty", mList.size(), 0);
@@ -139,16 +183,16 @@ public class SortedListTest extends TestCase {
@Test
public void testAdd() {
- Item item = new Item();
+ Item item = new Item(1);
assertEquals(insert(item), 0);
assertEquals(size(), 1);
assertTrue(mAdditions.contains(new Pair(0, 1)));
- Item item2 = new Item();
+ Item item2 = new Item(2);
item2.cmpField = item.cmpField + 1;
assertEquals(insert(item2), 1);
assertEquals(size(), 2);
assertTrue(mAdditions.contains(new Pair(1, 1)));
- Item item3 = new Item();
+ Item item3 = new Item(3);
item3.cmpField = item.cmpField - 1;
mAdditions.clear();
assertEquals(insert(item3), 0);
@@ -158,9 +202,8 @@ public class SortedListTest extends TestCase {
@Test
public void testAddDuplicate() {
- Item item = new Item();
- Item item2 = new Item(item.id, item.cmpField);
- item2.data = item.data;
+ Item item = new Item(1);
+ Item item2 = new Item(item.id);
insert(item);
assertEquals(0, insert(item2));
assertEquals(1, size());
@@ -170,7 +213,7 @@ public class SortedListTest extends TestCase {
@Test
public void testRemove() {
- Item item = new Item();
+ Item item = new Item(1);
assertFalse(remove(item));
assertEquals(0, mRemovals.size());
insert(item);
@@ -184,8 +227,8 @@ public class SortedListTest extends TestCase {
@Test
public void testRemove2() {
- Item item = new Item();
- Item item2 = new Item(item.cmpField);
+ Item item = new Item(1);
+ Item item2 = new Item(2, 1, 1);
insert(item);
assertFalse(remove(item2));
assertEquals(0, mRemovals.size());
@@ -218,11 +261,12 @@ public class SortedListTest extends TestCase {
Random random = new Random(System.nanoTime());
List<Item> copy = new ArrayList<Item>();
StringBuilder log = new StringBuilder();
+ int id = 1;
try {
for (int i = 0; i < 10000; i++) {
switch (random.nextInt(3)) {
case 0://ADD
- Item item = new Item();
+ Item item = new Item(id++);
copy.add(item);
insert(item);
log.append("add ").append(item).append("\n");
@@ -241,12 +285,13 @@ public class SortedListTest extends TestCase {
int index = random.nextInt(mList.size());
item = mList.get(index);
// TODO this cannot work
- Item newItem = new Item(item.id, item.cmpField);
- log.append("update ").append(item).append(" to ").append(newItem)
- .append("\n");
+ Item newItem =
+ new Item(item.id, item.cmpField, random.nextInt(1000));
while (newItem.data == item.data) {
newItem.data = random.nextInt(1000);
}
+ log.append("update ").append(item).append(" to ").append(newItem)
+ .append("\n");
int itemIndex = mList.add(newItem);
copy.remove(item);
copy.add(newItem);
@@ -258,10 +303,12 @@ public class SortedListTest extends TestCase {
if (copy.size() > 0) {
int index = random.nextInt(mList.size());
item = mList.get(index);
- Item newItem = new Item(item.id, random.nextInt());
+ Item newItem = new Item(item.id, random.nextInt(), random.nextInt());
mList.updateItemAt(index, newItem);
copy.remove(item);
copy.add(newItem);
+ log.append("update at ").append(index).append(" ").append(item)
+ .append(" to ").append(newItem).append("\n");
}
}
int lastCmp = Integer.MIN_VALUE;
@@ -299,14 +346,21 @@ public class SortedListTest extends TestCase {
Item[] items = new Item[count];
int id = idFrom;
for (int i = 0; i < count; i++) {
- Item item = new Item(id, id);
- item.data = id;
+ Item item = new Item(id);
items[i] = item;
id += idStep;
}
return items;
}
+ private static Item[] createItemsFromInts(int ... ints) {
+ Item[] items = new Item[ints.length];
+ for (int i = ints.length - 1; i >= 0; i--) {
+ items[i] = new Item(ints[i]);
+ }
+ return items;
+ }
+
private static Item[] shuffle(Item[] items) {
Random random = new Random(System.nanoTime());
final int count = items.length;
@@ -493,8 +547,7 @@ public class SortedListTest extends TestCase {
int uniqueId = 0;
for (int cmpField = 0; cmpField < maxCmpField; cmpField++) {
for (int id = 0; id < idsPerCmpField; id++) {
- Item item = new Item(uniqueId++, cmpField);
- item.data = generation;
+ Item item = new Item(uniqueId++, cmpField, generation);
items[index++] = item;
}
}
@@ -548,13 +601,13 @@ public class SortedListTest extends TestCase {
@Test
public void testAddAllStableSort() {
int id = 0;
- Item item = new Item(id++, 0);
+ Item item = new Item(id++, 0, 0);
mList.add(item);
// Create a few items with the same sort order.
Item[] items = new Item[3];
for (int i = 0; i < 3; i++) {
- items[i] = new Item(id++, item.cmpField);
+ items[i] = new Item(id++, item.cmpField, 0);
assertEquals(0, mCallback.compare(item, items[i]));
}
@@ -576,6 +629,7 @@ public class SortedListTest extends TestCase {
item.data = 1;
}
+
mInsertedCallback = new InsertedCallback<Item>() {
@Override
public void onInserted(int position, int count) {
@@ -585,6 +639,7 @@ public class SortedListTest extends TestCase {
assertEquals(i * 2, mList.get(i).id);
}
assertIntegrity(5, "onInserted(" + position + ", " + count + ")");
+
}
};
@@ -639,7 +694,7 @@ public class SortedListTest extends TestCase {
@Override
public void onInserted(int position, int count) {
try {
- mList.add(new Item());
+ mList.add(new Item(1));
fail("add must throw from within a callback");
} catch (IllegalStateException e) {
}
@@ -729,14 +784,14 @@ public class SortedListTest extends TestCase {
@Test
public void testAddExistingItemCallsChangeWithPayload() {
mList.addAll(
- new Item(1, 10),
- new Item(2, 20),
- new Item(3, 30)
+ new Item(1),
+ new Item(2),
+ new Item(3)
);
mPayloadChanges = true;
// add an item with the same id but a new data field i.e. send an update
- final Item twoUpdate = new Item(2, 20);
+ final Item twoUpdate = new Item(2);
twoUpdate.data = 1337;
mList.add(twoUpdate);
assertEquals(1, mPayloadUpdates.size());
@@ -750,14 +805,14 @@ public class SortedListTest extends TestCase {
@Test
public void testUpdateItemCallsChangeWithPayload() {
mList.addAll(
- new Item(1, 10),
- new Item(2, 20),
- new Item(3, 30)
+ new Item(1),
+ new Item(2),
+ new Item(3)
);
mPayloadChanges = true;
// add an item with the same id but a new data field i.e. send an update
- final Item twoUpdate = new Item(2, 20);
+ final Item twoUpdate = new Item(2);
twoUpdate.data = 1337;
mList.updateItemAt(1, twoUpdate);
assertEquals(1, mPayloadUpdates.size());
@@ -772,16 +827,16 @@ public class SortedListTest extends TestCase {
@Test
public void testAddMultipleExistingItemCallsChangeWithPayload() {
mList.addAll(
- new Item(1, 10),
- new Item(2, 20),
- new Item(3, 30)
+ new Item(1),
+ new Item(2),
+ new Item(3)
);
mPayloadChanges = true;
// add two items with the same ids but a new data fields i.e. send two updates
- final Item twoUpdate = new Item(2, 20);
+ final Item twoUpdate = new Item(2);
twoUpdate.data = 222;
- final Item threeUpdate = new Item(3, 30);
+ final Item threeUpdate = new Item(3);
threeUpdate.data = 333;
mList.addAll(twoUpdate, threeUpdate);
assertEquals(2, mPayloadUpdates.size());
@@ -796,6 +851,648 @@ public class SortedListTest extends TestCase {
assertEquals(3, size());
}
+ @Test
+ public void replaceAll_mayModifyInputFalse_doesNotModify() {
+ mList.addAll(
+ new Item(1),
+ new Item(2)
+ );
+ Item replacement0 = new Item(4);
+ Item replacement1 = new Item(3);
+ Item[] replacements = new Item[]{
+ replacement0,
+ replacement1
+ };
+
+ mList.replaceAll(replacements, false);
+
+ assertSame(replacement0, replacements[0]);
+ assertSame(replacement1, replacements[1]);
+ }
+
+ @Test
+ public void replaceAll_varArgs_isEquivalentToDefault() {
+ mList.addAll(
+ new Item(1),
+ new Item(2)
+ );
+ Item replacement0 = new Item(3);
+ Item replacement1 = new Item(4);
+
+ mList.replaceAll(replacement0, replacement1);
+
+ assertEquals(mList.get(0), replacement0);
+ assertEquals(mList.get(1), replacement1);
+ assertEquals(2, mList.size());
+ }
+
+ @Test
+ public void replaceAll_collection_isEquivalentToDefaultWithMayModifyInputFalse() {
+ mList.addAll(
+ new Item(1),
+ new Item(2)
+ );
+ Item replacement0 = new Item(4);
+ Item replacement1 = new Item(3);
+ List<Item> replacements = new ArrayList<>();
+ replacements.add(replacement0);
+ replacements.add(replacement1);
+
+ mList.replaceAll(replacements);
+
+ assertEquals(mList.get(0), replacement1);
+ assertEquals(mList.get(1), replacement0);
+ assertSame(replacements.get(0), replacement0);
+ assertSame(replacements.get(1), replacement1);
+ assertEquals(2, mList.size());
+ }
+
+ @Test
+ public void replaceAll_callsChangeWithPayload() {
+ mList.addAll(
+ new Item(1),
+ new Item(2),
+ new Item(3)
+ );
+ mPayloadChanges = true;
+ final Item twoUpdate = new Item(2);
+ twoUpdate.data = 222;
+ final Item threeUpdate = new Item(3);
+ threeUpdate.data = 333;
+
+ mList.replaceAll(twoUpdate, threeUpdate);
+
+ assertEquals(2, mPayloadUpdates.size());
+ final PayloadChange update1 = mPayloadUpdates.get(0);
+ assertEquals(0, update1.position);
+ assertEquals(1, update1.count);
+ assertEquals(222, update1.payload);
+ final PayloadChange update2 = mPayloadUpdates.get(1);
+ assertEquals(1, update2.position);
+ assertEquals(1, update2.count);
+ assertEquals(333, update2.payload);
+ }
+
+ @Test
+ public void replaceAll_totallyEquivalentData_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 2, 3);
+ Item[] items2 = createItemsFromInts(1, 2, 3);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mList.replaceAll(items2);
+
+ assertEquals(0, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ }
+
+ @Test
+ public void replaceAll_removalsAndAdds1_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 3, 5);
+ Item[] items2 = createItemsFromInts(2, 4);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 5)));
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 5)));
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 4, 5)));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(3));
+ assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(4));
+ assertEquals(5, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_removalsAndAdds2_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(2, 4);
+ Item[] items2 = createItemsFromInts(1, 3, 5);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 4)));
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3, 4)));
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3)));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(3));
+ assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(4));
+ assertEquals(5, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_removalsAndAdds3_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 3, 5);
+ Item[] items2 = createItemsFromInts(2, 3, 4);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 5)));
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 4, 5)));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(3));
+ assertEquals(4, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_removalsAndAdds4_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(2, 3, 4);
+ Item[] items2 = createItemsFromInts(1, 3, 5);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3, 4)));
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3)));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(3));
+ assertEquals(4, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_removalsAndAdds5_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 2, 3);
+ Item[] items2 = createItemsFromInts(3, 4, 5);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 2), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 1, 2), mEvents.get(1));
+ assertEquals(2, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_removalsAndAdds6_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(3, 4, 5);
+ Item[] items2 = createItemsFromInts(1, 2, 3);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.ADD, 0, 2), mEvents.get(0));
+ assertEquals(new Event(TYPE.REMOVE, 3, 2), mEvents.get(1));
+ assertEquals(2, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_move1_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 2, 3);
+ Item[] items2 = new Item[]{
+ new Item(2),
+ new Item(3),
+ new Item(1, 4, 1)};
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(1));
+ assertEquals(2, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_move2_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 2, 3);
+ Item[] items2 = new Item[]{
+ new Item(3, 0, 3),
+ new Item(1),
+ new Item(2)};
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(1));
+ assertEquals(2, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_move3_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9);
+ Item[] items2 = new Item[]{
+ new Item(3, 0, 3),
+ new Item(1),
+ new Item(5),
+ new Item(9),
+ new Item(7, 10, 7),
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(3, 0, 3),
+ new Item(1),
+ new Item(5),
+ new Item(7),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(3, 0, 3),
+ new Item(1),
+ new Item(5),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.ADD, 4, 1), mEvents.get(3));
+ assertEquals(4, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_move4_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9);
+ Item[] items2 = new Item[]{
+ new Item(3),
+ new Item(1, 4, 1),
+ new Item(5),
+ new Item(9, 6, 9),
+ new Item(7),
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(3),
+ new Item(1, 4, 1),
+ new Item(5),
+ new Item(7),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(3),
+ new Item(1, 4, 1),
+ new Item(5),
+ new Item(9, 6, 9),
+ new Item(7),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.ADD, 3, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.REMOVE, 5, 1), mEvents.get(3));
+ assertEquals(4, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_move5_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9);
+ Item[] items2 = new Item[]{
+ new Item(9, 1, 9),
+ new Item(7, 3, 7),
+ new Item(5),
+ new Item(3, 7, 3),
+ new Item(1, 9, 1),
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(9, 1, 9),
+ new Item(3),
+ new Item(5),
+ new Item(7),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(9, 1, 9),
+ new Item(5),
+ new Item(7),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(9, 1, 9),
+ new Item(7, 3, 7),
+ new Item(5),
+ new Item(7),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(9, 1, 9),
+ new Item(7, 3, 7),
+ new Item(5),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(9, 1, 9),
+ new Item(7, 3, 7),
+ new Item(5),
+ new Item(3, 7, 3),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(9, 1, 9),
+ new Item(7, 3, 7),
+ new Item(5),
+ new Item(3, 7, 3)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(3));
+ assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(4));
+ assertEquals(new Event(TYPE.ADD, 3, 1), mEvents.get(5));
+ assertEquals(new Event(TYPE.REMOVE, 4, 1), mEvents.get(6));
+ assertEquals(new Event(TYPE.ADD, 4, 1), mEvents.get(7));
+ assertEquals(8, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_orderSameItemDifferent_worksCorrectly() {
+ Item[] items1 = new Item[]{
+ new Item(1),
+ new Item(2, 3, 2),
+ new Item(5)
+ };
+ Item[] items2 = new Item[]{
+ new Item(1),
+ new Item(4, 3, 4),
+ new Item(5)
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1));
+ assertEquals(2, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_orderSameItemSameContentsDifferent_worksCorrectly() {
+ Item[] items1 = new Item[]{
+ new Item(1),
+ new Item(3, 3, 2),
+ new Item(5)
+ };
+ Item[] items2 = new Item[]{
+ new Item(1),
+ new Item(3, 3, 4),
+ new Item(5)
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.CHANGE, 1, 1), mEvents.get(0));
+ assertEquals(1, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_allTypesOfChanges1_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(2, 5, 6);
+ Item[] items2 = new Item[]{
+ new Item(1),
+ new Item(3, 2, 3),
+ new Item(6, 6, 7)
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 5, 6)));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(1),
+ new Item(3, 2, 3),
+ new Item(5),
+ new Item(6)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(1),
+ new Item(3, 2, 3),
+ new Item(6)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(3));
+ assertEquals(new Event(TYPE.CHANGE, 2, 1), mEvents.get(4));
+ assertEquals(5, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_allTypesOfChanges2_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 4, 6);
+ Item[] items2 = new Item[]{
+ new Item(1, 1, 2),
+ new Item(3),
+ new Item(5, 4, 5)
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(1, 1, 2),
+ new Item(3),
+ new Item(4),
+ new Item(6)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(1, 1, 2),
+ new Item(3),
+ new Item(6)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(1, 1, 2),
+ new Item(3),
+ new Item(5, 4, 5),
+ new Item(6)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.CHANGE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(3));
+ assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(4));
+ assertEquals(5, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_allTypesOfChanges3_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 2);
+ Item[] items2 = new Item[]{
+ new Item(2, 2, 3),
+ new Item(3, 2, 4),
+ new Item(5)
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(2, 2, 3)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.CHANGE, 0, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.ADD, 1, 2), mEvents.get(2));
+ assertEquals(3, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_newItemsAreIdentical_resultIsDeduped() {
+ Item[] items = createItemsFromInts(1, 1);
+ mList.replaceAll(items);
+
+ assertEquals(new Item(1), mList.get(0));
+ assertEquals(1, mList.size());
+ }
+
+ @Test
+ public void replaceAll_newItemsUnsorted_resultIsSorted() {
+ Item[] items = createItemsFromInts(2, 1);
+ mList.replaceAll(items);
+
+ assertEquals(new Item(1), mList.get(0));
+ assertEquals(new Item(2), mList.get(1));
+ assertEquals(2, mList.size());
+ }
+
+ @Test
+ public void replaceAll_calledAfterBeginBatchedUpdates_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 2, 3);
+ Item[] items2 = createItemsFromInts(4, 5, 6);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.beginBatchedUpdates();
+ mList.replaceAll(items2);
+ mList.endBatchedUpdates();
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 3), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 0, 3), mEvents.get(1));
+ assertEquals(2, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
private int size() {
return mList.size();
}
@@ -810,63 +1507,33 @@ public class SortedListTest extends TestCase {
static class Item {
- static int idCounter = 0;
final int id;
-
int cmpField;
+ int data;
- int data = (int) (Math.random() * 1000);//used for comparison
-
- public Item() {
- id = idCounter++;
- cmpField = (int) (Math.random() * 1000);
+ Item(int allFields) {
+ this(allFields, allFields, allFields);
}
- public Item(int cmpField) {
- id = idCounter++;
- this.cmpField = cmpField;
- }
-
- public Item(int id, int cmpField) {
+ Item(int id, int compField, int data) {
this.id = id;
- this.cmpField = cmpField;
+ this.cmpField = compField;
+ this.data = data;
}
@Override
public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
Item item = (Item) o;
- if (cmpField != item.cmpField) {
- return false;
- }
- if (id != item.id) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = id;
- result = 31 * result + cmpField;
- return result;
+ return id == item.id && cmpField == item.cmpField && data == item.data;
}
@Override
public String toString() {
- return "Item{" +
- "id=" + id +
- ", cmpField=" + cmpField +
- ", data=" + data +
- '}';
+ return "Item(id=" + id + ", cmpField=" + cmpField + ", data=" + data + ')';
}
}
@@ -913,6 +1580,85 @@ public class SortedListTest extends TestCase {
}
}
+ private enum TYPE {
+ ADD, REMOVE, MOVE, CHANGE
+ }
+
+ private final class AssertListStateRunnable implements Runnable {
+
+ private Item[] mExpectedItems;
+
+ AssertListStateRunnable(Item... expectedItems) {
+ this.mExpectedItems = expectedItems;
+ }
+
+ @Override
+ public void run() {
+ try {
+ assertEquals(mExpectedItems.length, mList.size());
+ for (int i = mExpectedItems.length - 1; i >= 0; i--) {
+ assertEquals(mExpectedItems[i], mList.get(i));
+ assertEquals(i, mList.indexOf(mExpectedItems[i]));
+ }
+ } catch (AssertionError assertionError) {
+ throw new AssertionError(
+ assertionError.getMessage()
+ + "\nExpected: "
+ + Arrays.toString(mExpectedItems)
+ + "\nActual: "
+ + sortedListToString(mList));
+ }
+ }
+ }
+
+ private static final class Event {
+ private final TYPE mType;
+ private final int mVal1;
+ private final int mVal2;
+
+ Event(TYPE type, int val1, int val2) {
+ this.mType = type;
+ this.mVal1 = val1;
+ this.mVal2 = val2;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Event that = (Event) o;
+ return mType == that.mType && mVal1 == that.mVal1 && mVal2 == that.mVal2;
+ }
+
+ @Override
+ public String toString() {
+ return "Event(" + mType + ", " + mVal1 + ", " + mVal2 + ")";
+ }
+ }
+
+ private <T> boolean sortedListEquals(SortedList<T> sortedList, T[] array) {
+ if (sortedList.size() != array.length) {
+ return false;
+ }
+ for (int i = sortedList.size() - 1; i >= 0; i--) {
+ if (!sortedList.get(i).equals(array[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static String sortedListToString(SortedList sortedList) {
+ StringBuilder stringBuilder = new StringBuilder("[");
+ int size = sortedList.size();
+ for (int i = 0; i < size; i++) {
+ stringBuilder.append(sortedList.get(i).toString() + ", ");
+ }
+ stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length());
+ stringBuilder.append("]");
+ return stringBuilder.toString();
+ }
+
private static final class PayloadChange {
public final int position;
public final int count;
diff --git a/android/support/v7/view/ContextThemeWrapper.java b/android/support/v7/view/ContextThemeWrapper.java
index aa5b36e9..cc634804 100644
--- a/android/support/v7/view/ContextThemeWrapper.java
+++ b/android/support/v7/view/ContextThemeWrapper.java
@@ -16,26 +16,19 @@
package android.support.v7.view;
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
-import android.support.annotation.RestrictTo;
import android.support.annotation.StyleRes;
import android.support.v7.appcompat.R;
import android.view.LayoutInflater;
/**
- * A ContextWrapper that allows you to modify the theme from what is in the
- * wrapped context.
- *
- * @hide
+ * A context wrapper that allows you to modify or replace the theme of the wrapped context.
*/
-@RestrictTo(LIBRARY_GROUP)
public class ContextThemeWrapper extends ContextWrapper {
private int mThemeResource;
private Resources.Theme mTheme;
@@ -110,15 +103,6 @@ public class ContextThemeWrapper extends ContextWrapper {
mOverrideConfiguration = new Configuration(overrideConfiguration);
}
- /**
- * Used by ActivityThread to apply the overridden configuration to onConfigurationChange
- * callbacks.
- * @hide
- */
- public Configuration getOverrideConfiguration() {
- return mOverrideConfiguration;
- }
-
@Override
public Resources getResources() {
return getResourcesInternal();
@@ -144,6 +128,10 @@ public class ContextThemeWrapper extends ContextWrapper {
}
}
+ /**
+ * Returns the resource ID of the theme that is to be applied on top of the base context's
+ * theme.
+ */
public int getThemeResId() {
return mThemeResource;
}
diff --git a/android/support/v7/widget/AppCompatAutoCompleteTextView.java b/android/support/v7/widget/AppCompatAutoCompleteTextView.java
index 5b0a2f8d..e41bec75 100644
--- a/android/support/v7/widget/AppCompatAutoCompleteTextView.java
+++ b/android/support/v7/widget/AppCompatAutoCompleteTextView.java
@@ -29,6 +29,8 @@ import android.support.v4.view.TintableBackgroundView;
import android.support.v7.appcompat.R;
import android.support.v7.content.res.AppCompatResources;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.AutoCompleteTextView;
/**
@@ -177,4 +179,10 @@ public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implemen
mTextHelper.onSetTextAppearance(context, resId);
}
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/android/support/v7/widget/AppCompatCheckedTextView.java b/android/support/v7/widget/AppCompatCheckedTextView.java
index 921f0a26..dca409c9 100644
--- a/android/support/v7/widget/AppCompatCheckedTextView.java
+++ b/android/support/v7/widget/AppCompatCheckedTextView.java
@@ -20,6 +20,8 @@ import android.content.Context;
import android.support.annotation.DrawableRes;
import android.support.v7.content.res.AppCompatResources;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.CheckedTextView;
/**
@@ -79,4 +81,10 @@ public class AppCompatCheckedTextView extends CheckedTextView {
mTextHelper.applyCompoundDrawablesTints();
}
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/android/support/v7/widget/AppCompatEditText.java b/android/support/v7/widget/AppCompatEditText.java
index 406e364e..6831fcbf 100644
--- a/android/support/v7/widget/AppCompatEditText.java
+++ b/android/support/v7/widget/AppCompatEditText.java
@@ -28,6 +28,8 @@ import android.support.annotation.RestrictTo;
import android.support.v4.view.TintableBackgroundView;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.EditText;
/**
@@ -159,4 +161,10 @@ public class AppCompatEditText extends EditText implements TintableBackgroundVie
mTextHelper.onSetTextAppearance(context, resId);
}
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/android/support/v7/widget/AppCompatHintHelper.java b/android/support/v7/widget/AppCompatHintHelper.java
new file mode 100644
index 00000000..0d30fb7c
--- /dev/null
+++ b/android/support/v7/widget/AppCompatHintHelper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.view.View;
+import android.view.ViewParent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+class AppCompatHintHelper {
+
+ static InputConnection onCreateInputConnection(InputConnection ic, EditorInfo outAttrs,
+ View view) {
+ if (ic != null && outAttrs.hintText == null) {
+ // If we don't have a hint and the parent implements WithHint, use its hint for the
+ // EditorInfo. This allows us to display a hint in 'extract mode'.
+ ViewParent parent = view.getParent();
+ while (parent instanceof View) {
+ if (parent instanceof WithHint) {
+ outAttrs.hintText = ((WithHint) parent).getHint();
+ break;
+ }
+ parent = parent.getParent();
+ }
+ }
+ return ic;
+ }
+
+}
diff --git a/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java b/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java
index 8060d7d1..b71b08a5 100644
--- a/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java
+++ b/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java
@@ -29,6 +29,8 @@ import android.support.v4.view.TintableBackgroundView;
import android.support.v7.appcompat.R;
import android.support.v7.content.res.AppCompatResources;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.MultiAutoCompleteTextView;
/**
@@ -177,4 +179,10 @@ public class AppCompatMultiAutoCompleteTextView extends MultiAutoCompleteTextVie
mTextHelper.onSetTextAppearance(context, resId);
}
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/android/support/v7/widget/AppCompatTextView.java b/android/support/v7/widget/AppCompatTextView.java
index cfa6a2a9..d8132770 100644
--- a/android/support/v7/widget/AppCompatTextView.java
+++ b/android/support/v7/widget/AppCompatTextView.java
@@ -31,6 +31,8 @@ import android.support.v4.widget.AutoSizeableTextView;
import android.support.v4.widget.TextViewCompat;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.TextView;
/**
@@ -361,4 +363,10 @@ public class AppCompatTextView extends TextView implements TintableBackgroundVie
}
return new int[0];
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/android/support/v7/widget/RecyclerView.java b/android/support/v7/widget/RecyclerView.java
index 4bc17a86..84c28b10 100644
--- a/android/support/v7/widget/RecyclerView.java
+++ b/android/support/v7/widget/RecyclerView.java
@@ -386,8 +386,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners;
/**
- * Set to true when an adapter data set changed notification is received.
- * In that case, we cannot run any animations since we don't know what happened until layout.
+ * True after an event occurs that signals that the entire data set has changed. In that case,
+ * we cannot run any animations since we don't know what happened until layout.
*
* Attached items are invalid until next layout, at which point layout will animate/replace
* items as necessary, building up content from the (effectively) new adapter from scratch.
@@ -395,11 +395,20 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
* Cached items must be discarded when setting this to true, so that the cache may be freely
* used by prefetching until the next layout occurs.
*
- * @see #setDataSetChangedAfterLayout()
+ * @see #processDataSetCompletelyChanged(boolean)
*/
boolean mDataSetHasChangedAfterLayout = false;
/**
+ * True after the data set has completely changed and
+ * {@link LayoutManager#onItemsChanged(RecyclerView)} should be called during the subsequent
+ * measure/layout.
+ *
+ * @see #processDataSetCompletelyChanged(boolean)
+ */
+ boolean mDispatchItemsChangedEvent = false;
+
+ /**
* This variable is incremented during a dispatchLayout and/or scroll.
* Some methods should not be called during these periods (e.g. adapter data change).
* Doing so will create hard to find bugs so we better check it and throw an exception.
@@ -1044,6 +1053,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, true, removeAndRecycleExistingViews);
+ processDataSetCompletelyChanged(true);
requestLayout();
}
/**
@@ -1059,6 +1069,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
+ processDataSetCompletelyChanged(false);
requestLayout();
}
@@ -1112,7 +1123,6 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
- setDataSetChangedAfterLayout();
}
/**
@@ -2509,9 +2519,17 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
if (next == null || next == this) {
return false;
}
+ // panic, result view is not a child anymore, maybe workaround b/37864393
+ if (findContainingItemView(next) == null) {
+ return false;
+ }
if (focused == null) {
return true;
}
+ // panic, focused view is not a child anymore, maybe workaround b/37864393
+ if (findContainingItemView(focused) == null) {
+ return true;
+ }
mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
mTempRect2.set(0, 0, next.getWidth(), next.getHeight());
@@ -3221,7 +3239,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
/**
- * Used when onMeasure is called before layout manager is set
+ * An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios
+ * where this RecyclerView is otherwise lacking better information.
*/
void defaultOnMeasure(int widthSpec, int heightSpec) {
// calling LayoutManager here is not pretty but that API is already public and it is better
@@ -3398,7 +3417,9 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
// Processing these items have no value since data set changed unexpectedly.
// Instead, we just reset it.
mAdapterHelper.reset();
- mLayout.onItemsChanged(this);
+ if (mDispatchItemsChangedEvent) {
+ mLayout.onItemsChanged(this);
+ }
}
// simple animations are a subset of advanced animations (which will cause a
// pre-layout step)
@@ -3821,6 +3842,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = false;
+ mDispatchItemsChangedEvent = false;
mState.mRunSimpleAnimations = false;
mState.mRunPredictiveAnimations = false;
@@ -4288,19 +4310,21 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
viewHolder.getUnmodifiedPayloads());
}
-
/**
- * Call this method to signal that *all* adapter content has changed (generally, because of
- * setAdapter, swapAdapter, or notifyDataSetChanged), and that once layout occurs, all
- * attached items should be discarded or animated.
+ * Processes the fact that, as far as we can tell, the data set has completely changed.
*
- * Attached items are labeled as invalid, and all cached items are discarded.
+ * <ul>
+ * <li>Once layout occurs, all attached items should be discarded or animated.
+ * <li>Attached items are labeled as invalid.
+ * <li>Because items may still be prefetched between a "data set completely changed"
+ * event and a layout event, all cached items are discarded.
+ * </ul>
*
- * It is still possible for items to be prefetched while mDataSetHasChangedAfterLayout == true,
- * so this method must always discard all cached views so that the only valid items that remain
- * in the cache, once layout occurs, are valid prefetched items.
+ * @param dispatchItemsChanged Whether to call
+ * {@link LayoutManager#onItemsChanged(RecyclerView)} during measure/layout.
*/
- void setDataSetChangedAfterLayout() {
+ void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
+ mDispatchItemsChangedEvent |= dispatchItemsChanged;
mDataSetHasChangedAfterLayout = true;
markKnownViewsInvalid();
}
@@ -5110,7 +5134,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
- setDataSetChangedAfterLayout();
+ processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
@@ -7419,9 +7443,10 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
* wants to handle the layout measurements itself.
* <p>
* This method is usually called by the LayoutManager with value {@code true} if it wants
- * to support WRAP_CONTENT. If you are using a public LayoutManager but want to customize
- * the measurement logic, you can call this method with {@code false} and override
- * {@link LayoutManager#onMeasure(int, int)} to implement your custom measurement logic.
+ * to support {@link ViewGroup.LayoutParams#WRAP_CONTENT}. If you are using a public
+ * LayoutManager but want to customize the measurement logic, you can call this method with
+ * {@code false} and override {@link LayoutManager#onMeasure(Recycler, State, int, int)} to
+ * implement your custom measurement logic.
* <p>
* AutoMeasure is a convenience mechanism for LayoutManagers to easily wrap their content or
* handle various specs provided by the RecyclerView's parent.
@@ -7495,24 +7520,26 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
/**
- * Returns whether this LayoutManager supports automatic item animations.
- * A LayoutManager wishing to support item animations should obey certain
- * rules as outlined in {@link #onLayoutChildren(Recycler, State)}.
- * The default return value is <code>false</code>, so subclasses of LayoutManager
- * will not get predictive item animations by default.
- *
- * <p>Whether item animations are enabled in a RecyclerView is determined both
- * by the return value from this method and the
+ * Returns whether this LayoutManager supports "predictive item animations".
+ * <p>
+ * "Predictive item animations" are automatically created animations that show
+ * where items came from, and where they are going to, as items are added, removed,
+ * or moved within a layout.
+ * <p>
+ * A LayoutManager wishing to support predictive item animations must override this
+ * method to return true (the default implementation returns false) and must obey certain
+ * behavioral contracts outlined in {@link #onLayoutChildren(Recycler, State)}.
+ * <p>
+ * Whether item animations actually occur in a RecyclerView is actually determined by both
+ * the return value from this method and the
* {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the
* RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this
- * method returns false, then simple item animations will be enabled, in which
- * views that are moving onto or off of the screen are simply faded in/out. If
- * the RecyclerView has a non-null ItemAnimator and this method returns true,
- * then there will be two calls to {@link #onLayoutChildren(Recycler, State)} to
- * setup up the information needed to more intelligently predict where appearing
- * and disappearing views should be animated from/to.</p>
+ * method returns false, then only "simple item animations" will be enabled in the
+ * RecyclerView, in which views whose position are changing are simply faded in/out. If the
+ * RecyclerView has a non-null ItemAnimator and this method returns true, then predictive
+ * item animations will be enabled in the RecyclerView.
*
- * @return true if predictive item animations should be enabled, false otherwise
+ * @return true if this LayoutManager supports predictive item animations, false otherwise.
*/
public boolean supportsPredictiveItemAnimations() {
return false;
@@ -9491,9 +9518,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
/**
- * Called if the RecyclerView this LayoutManager is bound to has a different adapter set.
- * The LayoutManager may use this opportunity to clear caches and configure state such
- * that it can relayout appropriately with the new data and potentially new view types.
+ * Called if the RecyclerView this LayoutManager is bound to has a different adapter set via
+ * {@link RecyclerView#setAdapter(Adapter)} or
+ * {@link RecyclerView#swapAdapter(Adapter, boolean)}. The LayoutManager may use this
+ * opportunity to clear caches and configure state such that it can relayout appropriately
+ * with the new data and potentially new view types.
*
* <p>The default implementation removes all currently attached views.</p>
*
@@ -9535,8 +9564,9 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
/**
- * Called when {@link Adapter#notifyDataSetChanged()} is triggered instead of giving
- * detailed information on what has actually changed.
+ * Called in response to a call to {@link Adapter#notifyDataSetChanged()} or
+ * {@link RecyclerView#swapAdapter(Adapter, boolean)} ()} and signals that the the entire
+ * data set has changed.
*
* @param recyclerView
*/
@@ -10042,7 +10072,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
if (vScroll == 0 && hScroll == 0) {
return false;
}
- mRecyclerView.scrollBy(hScroll, vScroll);
+ mRecyclerView.smoothScrollBy(hScroll, vScroll);
return true;
}
@@ -11794,6 +11824,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
boolean mStructureChanged = false;
+ /**
+ * True if the associated {@link RecyclerView} is in the pre-layout step where it is having
+ * its {@link LayoutManager} layout items where they will be at the beginning of a set of
+ * predictive item animations.
+ */
boolean mInPreLayout = false;
boolean mTrackOldChangeHolders = false;
@@ -11869,8 +11904,9 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
/**
- * Returns true if
- * @return
+ * Returns true if the {@link RecyclerView} is in the pre-layout step where it is having its
+ * {@link LayoutManager} layout items where they will be at the beginning of a set of
+ * predictive item animations.
*/
public boolean isPreLayout() {
return mInPreLayout;
diff --git a/android/support/v7/widget/Toolbar.java b/android/support/v7/widget/Toolbar.java
index 45e25830..f383e90c 100644
--- a/android/support/v7/widget/Toolbar.java
+++ b/android/support/v7/widget/Toolbar.java
@@ -56,6 +56,7 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -2366,12 +2367,20 @@ public class Toolbar extends ViewGroup {
@Override
public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
ensureCollapseButtonView();
- if (mCollapseButtonView.getParent() != Toolbar.this) {
+ ViewParent collapseButtonParent = mCollapseButtonView.getParent();
+ if (collapseButtonParent != Toolbar.this) {
+ if (collapseButtonParent instanceof ViewGroup) {
+ ((ViewGroup) collapseButtonParent).removeView(mCollapseButtonView);
+ }
addView(mCollapseButtonView);
}
mExpandedActionView = item.getActionView();
mCurrentExpandedItem = item;
- if (mExpandedActionView.getParent() != Toolbar.this) {
+ ViewParent expandedActionParent = mExpandedActionView.getParent();
+ if (expandedActionParent != Toolbar.this) {
+ if (expandedActionParent instanceof ViewGroup) {
+ ((ViewGroup) expandedActionParent).removeView(mExpandedActionView);
+ }
final LayoutParams lp = generateDefaultLayoutParams();
lp.gravity = GravityCompat.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
lp.mViewType = LayoutParams.EXPANDED;
diff --git a/android/support/v7/widget/TooltipPopup.java b/android/support/v7/widget/TooltipPopup.java
index dc20aa1f..396fe058 100644
--- a/android/support/v7/widget/TooltipPopup.java
+++ b/android/support/v7/widget/TooltipPopup.java
@@ -31,6 +31,7 @@ import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;
@@ -99,6 +100,7 @@ class TooltipPopup {
private void computePosition(View anchorView, int anchorX, int anchorY, boolean fromTouch,
WindowManager.LayoutParams outParams) {
+ outParams.token = anchorView.getApplicationWindowToken();
final int tooltipPreciseAnchorThreshold = mContext.getResources().getDimensionPixelOffset(
R.dimen.tooltip_precise_anchor_threshold);
@@ -157,7 +159,7 @@ class TooltipPopup {
mTmpAnchorPos[1] -= mTmpAppPos[1];
// mTmpAnchorPos is now relative to the main app window.
- outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2;
+ outParams.x = mTmpAnchorPos[0] + offsetX - appView.getWidth() / 2;
final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mContentView.measure(spec, spec);
@@ -181,6 +183,16 @@ class TooltipPopup {
}
private static View getAppRootView(View anchorView) {
+ View rootView = anchorView.getRootView();
+ ViewGroup.LayoutParams lp = rootView.getLayoutParams();
+ if (lp instanceof WindowManager.LayoutParams
+ && (((WindowManager.LayoutParams) lp).type
+ == WindowManager.LayoutParams.TYPE_APPLICATION)) {
+ // This covers regular app windows and Dialog windows.
+ return rootView;
+ }
+ // For non-application window types (such as popup windows) try to find the main app window
+ // through the context.
Context context = anchorView.getContext();
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
@@ -189,6 +201,8 @@ class TooltipPopup {
context = ((ContextWrapper) context).getBaseContext();
}
}
- return anchorView.getRootView();
+ // Main app window not found, fall back to the anchor's root view. There is no guarantee
+ // that the tooltip position will be computed correctly.
+ return rootView;
}
}
diff --git a/android/support/v7/widget/WithHint.java b/android/support/v7/widget/WithHint.java
new file mode 100644
index 00000000..d14f483a
--- /dev/null
+++ b/android/support/v7/widget/WithHint.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public interface WithHint {
+ /**
+ * Returns the hint which is displayed in the floating label, if enabled.
+ *
+ * @return the hint, or null if there isn't one set, or the hint is not enabled.
+ */
+ @Nullable
+ CharSequence getHint();
+}
diff --git a/android/support/wear/ambient/AmbientMode.java b/android/support/wear/ambient/AmbientMode.java
index 5db93830..0077a5bd 100644
--- a/android/support/wear/ambient/AmbientMode.java
+++ b/android/support/wear/ambient/AmbientMode.java
@@ -21,7 +21,9 @@ import android.app.FragmentManager;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.CallSuper;
+import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
+import android.util.Log;
import com.google.android.wearable.compat.WearableActivityController;
@@ -48,6 +50,7 @@ import java.io.PrintWriter;
* }</pre>
*/
public final class AmbientMode extends Fragment {
+ private static final String TAG = "AmbientMode";
/**
* Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
@@ -104,9 +107,6 @@ public final class AmbientMode extends Fragment {
* running (after onResume, before onPause). All drawing should complete by the conclusion
* of this method. Note that {@code invalidate()} calls will be executed before resuming
* lower-power mode.
- * <p>
- * <p><em>Derived classes must call through to the super class's implementation of this
- * method. If they do not, an exception will be thrown.</em>
*
* @param ambientDetails bundle containing information about the display being used.
* It includes information about low-bit color and burn-in protection.
@@ -122,9 +122,6 @@ public final class AmbientMode extends Fragment {
/**
* Called when an activity should exit ambient mode. This event is sent while an activity is
* running (after onResume, before onPause).
- * <p>
- * <p><em>Derived classes must call through to the super class's implementation of this
- * method. If they do not, an exception will be thrown.</em>
*/
public void onExitAmbient() {}
}
@@ -133,20 +130,27 @@ public final class AmbientMode extends Fragment {
new AmbientDelegate.AmbientCallback() {
@Override
public void onEnterAmbient(Bundle ambientDetails) {
- mSuppliedCallback.onEnterAmbient(ambientDetails);
+ if (mSuppliedCallback != null) {
+ mSuppliedCallback.onEnterAmbient(ambientDetails);
+ }
}
@Override
public void onExitAmbient() {
- mSuppliedCallback.onExitAmbient();
+ if (mSuppliedCallback != null) {
+ mSuppliedCallback.onExitAmbient();
+ }
}
@Override
public void onUpdateAmbient() {
- mSuppliedCallback.onUpdateAmbient();
+ if (mSuppliedCallback != null) {
+ mSuppliedCallback.onUpdateAmbient();
+ }
}
};
private AmbientDelegate mDelegate;
+ @Nullable
private AmbientCallback mSuppliedCallback;
private AmbientController mController;
@@ -166,8 +170,7 @@ public final class AmbientMode extends Fragment {
if (context instanceof AmbientCallbackProvider) {
mSuppliedCallback = ((AmbientCallbackProvider) context).getAmbientCallback();
} else {
- throw new IllegalArgumentException(
- "fragment should attach to an activity that implements AmbientCallback");
+ Log.w(TAG, "No callback provided - enabling only smart resume");
}
}
@@ -176,7 +179,9 @@ public final class AmbientMode extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDelegate.onCreate();
- mDelegate.setAmbientEnabled();
+ if (mSuppliedCallback != null) {
+ mDelegate.setAmbientEnabled();
+ }
}
@Override
@@ -215,15 +220,19 @@ public final class AmbientMode extends Fragment {
}
/**
- * Attach ambient support to the given activity.
+ * Attach ambient support to the given activity. Calling this method with an Activity
+ * implementing the {@link AmbientCallbackProvider} interface will provide you with an
+ * opportunity to react to ambient events such as {@code onEnterAmbient}. Alternatively,
+ * you can call this method with an Activity which does not implement
+ * the {@link AmbientCallbackProvider} interface and that will only enable the auto-resume
+ * functionality. This is equivalent to providing (@code null} from
+ * the {@link AmbientCallbackProvider}.
*
- * @param activity the activity to attach ambient support to. This activity has to also
- * implement {@link AmbientCallbackProvider}
+ * @param activity the activity to attach ambient support to.
* @return the associated {@link AmbientController} which can be used to query the state of
* ambient mode.
*/
- public static <T extends Activity & AmbientCallbackProvider> AmbientController
- attachAmbientSupport(T activity) {
+ public static <T extends Activity> AmbientController attachAmbientSupport(T activity) {
FragmentManager fragmentManager = activity.getFragmentManager();
AmbientMode ambientFragment = (AmbientMode) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
if (ambientFragment == null) {
diff --git a/android/system/Os.java b/android/system/Os.java
index fe8f7d45..cc24cc5e 100644
--- a/android/system/Os.java
+++ b/android/system/Os.java
@@ -16,8 +16,6 @@
package android.system;
-import android.util.MutableInt;
-import android.util.MutableLong;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.net.InetAddress;
@@ -473,27 +471,6 @@ public final class Os {
/**
* See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>.
- *
- * @deprecated This method will be removed in a future version of Android. Use
- * {@link #sendfile(FileDescriptor, FileDescriptor, Int64Ref, long)} instead.
- */
- @Deprecated
- public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException {
- if (inOffset == null) {
- return Libcore.os.sendfile(outFd, inFd, null, byteCount);
- } else {
- libcore.util.MutableLong internalInOffset = new libcore.util.MutableLong(
- inOffset.value);
- try {
- return Libcore.os.sendfile(outFd, inFd, internalInOffset, byteCount);
- } finally {
- inOffset.value = internalInOffset.value;
- }
- }
- }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>.
*/
public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, Int64Ref inOffset, long byteCount) throws ErrnoException {
if (inOffset == null) {
@@ -672,26 +649,6 @@ public final class Os {
/**
* See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>.
*
- * @deprecated This method will be removed in a future version of Android. Use
- * {@link #waitpid(int, Int32Ref, int)} instead.
- */
- @Deprecated
- public static int waitpid(int pid, MutableInt status, int options) throws ErrnoException {
- if (status == null) {
- return Libcore.os.waitpid(pid, null, options);
- } else {
- libcore.util.MutableInt internalStatus = new libcore.util.MutableInt(status.value);
- try {
- return Libcore.os.waitpid(pid, internalStatus, options);
- } finally {
- status.value = internalStatus.value;
- }
- }
- }
-
- /**
- * See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>.
- *
* @throws IllegalArgumentException if {@code status != null && status.length != 1}
*/
public static int waitpid(int pid, Int32Ref status, int options) throws ErrnoException {
diff --git a/android/telecom/RemoteConnection.java b/android/telecom/RemoteConnection.java
index f30d7bde..05480dc3 100644
--- a/android/telecom/RemoteConnection.java
+++ b/android/telecom/RemoteConnection.java
@@ -1065,7 +1065,7 @@ public final class RemoteConnection {
*
* @param state The audio state of this {@code RemoteConnection}.
* @hide
- * @deprecated Use {@link #setCallAudioState(CallAudioState) instead.
+ * @deprecated Use {@link #setCallAudioState(CallAudioState)} instead.
*/
@SystemApi
@Deprecated
diff --git a/android/telecom/TelecomManager.java b/android/telecom/TelecomManager.java
index da32e0be..92d458f1 100644
--- a/android/telecom/TelecomManager.java
+++ b/android/telecom/TelecomManager.java
@@ -1346,7 +1346,7 @@ public class TelecomManager {
/**
* Returns whether TTY is supported on this device.
*/
-
+ @SystemApi
@RequiresPermission(anyOf = {
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
android.Manifest.permission.READ_PHONE_STATE
diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java
index 1db6ef7b..69371a18 100644
--- a/android/telephony/CarrierConfigManager.java
+++ b/android/telephony/CarrierConfigManager.java
@@ -816,6 +816,14 @@ public class CarrierConfigManager {
public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
/**
+ * Determines whether manage IMS conference calls is supported by a carrier. When {@code true},
+ * manage IMS conference call is supported, {@code false otherwise}.
+ * @hide
+ */
+ public static final String KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL =
+ "support_manage_ims_conference_call_bool";
+
+ /**
* Determines whether High Definition audio property is displayed in the dialer UI.
* If {@code false}, remove the HD audio property from the connection so that HD audio related
* UI is not displayed. If {@code true}, keep HD audio property as it is configured.
@@ -1814,6 +1822,7 @@ public class CarrierConfigManager {
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);
+ sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false);
sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5);
diff --git a/android/telephony/CellIdentityGsm.java b/android/telephony/CellIdentityGsm.java
index 6276626a..c9684062 100644
--- a/android/telephony/CellIdentityGsm.java
+++ b/android/telephony/CellIdentityGsm.java
@@ -101,9 +101,6 @@ public final class CellIdentityGsm implements Parcelable {
* @param alphal long alpha Operator Name String or Enhanced Operator Name String
* @param alphas short alpha Operator Name String or Enhanced Operator Name String
*
- * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
- * not a 2 or 3-digit code.
- *
* @hide
*/
public CellIdentityGsm (int lac, int cid, int arfcn, int bsic, String mccStr,
@@ -115,22 +112,29 @@ public final class CellIdentityGsm implements Parcelable {
// for inbound parcels
mBsic = (bsic == 0xFF) ? Integer.MAX_VALUE : bsic;
+ // Only allow INT_MAX if unknown string mcc/mnc
if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
mMccStr = mccStr;
- } else if (mccStr.isEmpty()) {
- // If the mccStr parsed from Parcel is empty, set it as null.
+ } else if (mccStr.isEmpty() || mccStr.equals(String.valueOf(Integer.MAX_VALUE))) {
+ // If the mccStr is empty or unknown, set it as null.
mMccStr = null;
} else {
- throw new IllegalArgumentException("invalid MCC format");
+ // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MCC format
+ // after the bug got fixed.
+ mMccStr = null;
+ log("invalid MCC format: " + mccStr);
}
if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
mMncStr = mncStr;
- } else if (mncStr.isEmpty()) {
- // If the mncStr parsed from Parcel is empty, set it as null.
+ } else if (mncStr.isEmpty() || mncStr.equals(String.valueOf(Integer.MAX_VALUE))) {
+ // If the mncStr is empty or unknown, set it as null.
mMncStr = null;
} else {
- throw new IllegalArgumentException("invalid MNC format");
+ // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MNC format
+ // after the bug got fixed.
+ mMncStr = null;
+ log("invalid MNC format: " + mncStr);
}
mAlphaLong = alphal;
diff --git a/android/telephony/CellIdentityLte.java b/android/telephony/CellIdentityLte.java
index 74d2966b..825dcc33 100644
--- a/android/telephony/CellIdentityLte.java
+++ b/android/telephony/CellIdentityLte.java
@@ -102,9 +102,6 @@ public final class CellIdentityLte implements Parcelable {
* @param alphal long alpha Operator Name String or Enhanced Operator Name String
* @param alphas short alpha Operator Name String or Enhanced Operator Name String
*
- * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
- * not a 2 or 3-digit code.
- *
* @hide
*/
public CellIdentityLte (int ci, int pci, int tac, int earfcn, String mccStr,
@@ -114,22 +111,29 @@ public final class CellIdentityLte implements Parcelable {
mTac = tac;
mEarfcn = earfcn;
+ // Only allow INT_MAX if unknown string mcc/mnc
if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
mMccStr = mccStr;
- } else if (mccStr.isEmpty()) {
- // If the mccStr parsed from Parcel is empty, set it as null.
+ } else if (mccStr.isEmpty() || mccStr.equals(String.valueOf(Integer.MAX_VALUE))) {
+ // If the mccStr is empty or unknown, set it as null.
mMccStr = null;
} else {
- throw new IllegalArgumentException("invalid MCC format");
+ // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MCC format
+ // after the bug got fixed.
+ mMccStr = null;
+ log("invalid MCC format: " + mccStr);
}
if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
mMncStr = mncStr;
- } else if (mncStr.isEmpty()) {
- // If the mncStr parsed from Parcel is empty, set it as null.
+ } else if (mncStr.isEmpty() || mncStr.equals(String.valueOf(Integer.MAX_VALUE))) {
+ // If the mncStr is empty or unknown, set it as null.
mMncStr = null;
} else {
- throw new IllegalArgumentException("invalid MNC format");
+ // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MNC format
+ // after the bug got fixed.
+ mMncStr = null;
+ log("invalid MNC format: " + mncStr);
}
mAlphaLong = alphal;
diff --git a/android/telephony/CellIdentityWcdma.java b/android/telephony/CellIdentityWcdma.java
index 51b11aa8..e74b5701 100644
--- a/android/telephony/CellIdentityWcdma.java
+++ b/android/telephony/CellIdentityWcdma.java
@@ -102,9 +102,6 @@ public final class CellIdentityWcdma implements Parcelable {
* @param alphal long alpha Operator Name String or Enhanced Operator Name String
* @param alphas short alpha Operator Name String or Enhanced Operator Name String
*
- * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
- * not a 2 or 3-digit code.
- *
* @hide
*/
public CellIdentityWcdma (int lac, int cid, int psc, int uarfcn,
@@ -114,22 +111,29 @@ public final class CellIdentityWcdma implements Parcelable {
mPsc = psc;
mUarfcn = uarfcn;
+ // Only allow INT_MAX if unknown string mcc/mnc
if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
mMccStr = mccStr;
- } else if (mccStr.isEmpty()) {
- // If the mccStr parsed from Parcel is empty, set it as null.
+ } else if (mccStr.isEmpty() || mccStr.equals(String.valueOf(Integer.MAX_VALUE))) {
+ // If the mccStr is empty or unknown, set it as null.
mMccStr = null;
} else {
- throw new IllegalArgumentException("invalid MCC format");
+ // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MCC format
+ // after the bug got fixed.
+ mMccStr = null;
+ log("invalid MCC format: " + mccStr);
}
if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
mMncStr = mncStr;
- } else if (mncStr.isEmpty()) {
- // If the mncStr parsed from Parcel is empty, set it as null.
+ } else if (mncStr.isEmpty() || mncStr.equals(String.valueOf(Integer.MAX_VALUE))) {
+ // If the mncStr is empty or unknown, set it as null.
mMncStr = null;
} else {
- throw new IllegalArgumentException("invalid MNC format");
+ // TODO: b/69384059 Should throw IllegalArgumentException for the invalid MNC format
+ // after the bug got fixed.
+ mMncStr = null;
+ log("invalid MNC format: " + mncStr);
}
mAlphaLong = alphal;
diff --git a/android/telephony/SmsManager.java b/android/telephony/SmsManager.java
index 5d039268..fdedf758 100644
--- a/android/telephony/SmsManager.java
+++ b/android/telephony/SmsManager.java
@@ -350,6 +350,7 @@ public final class SmsManager {
*
* @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
*/
+ @SystemApi
public void sendTextMessageWithoutPersisting(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
diff --git a/android/telephony/SubscriptionManager.java b/android/telephony/SubscriptionManager.java
index 2f39ddb1..1e6abf22 100644
--- a/android/telephony/SubscriptionManager.java
+++ b/android/telephony/SubscriptionManager.java
@@ -28,6 +28,7 @@ import android.content.res.Resources;
import android.net.INetworkPolicyManager;
import android.net.Uri;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -361,6 +362,9 @@ public class SubscriptionManager {
/**
* TelephonyProvider column name for enable Volte.
+ *
+ * If this setting is not initialized (set to -1) then we use the Carrier Config value
+ * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
*@hide
*/
public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
@@ -446,7 +450,15 @@ public class SubscriptionManager {
* for #onSubscriptionsChanged to be invoked.
*/
public static class OnSubscriptionsChangedListener {
- private final Handler mHandler = new Handler() {
+ private class OnSubscriptionsChangedListenerHandler extends Handler {
+ OnSubscriptionsChangedListenerHandler() {
+ super();
+ }
+
+ OnSubscriptionsChangedListenerHandler(Looper looper) {
+ super(looper);
+ }
+
@Override
public void handleMessage(Message msg) {
if (DBG) {
@@ -454,7 +466,22 @@ public class SubscriptionManager {
}
OnSubscriptionsChangedListener.this.onSubscriptionsChanged();
}
- };
+ }
+
+ private final Handler mHandler;
+
+ public OnSubscriptionsChangedListener() {
+ mHandler = new OnSubscriptionsChangedListenerHandler();
+ }
+
+ /**
+ * Allow a listener to be created with a custom looper
+ * @param looper the looper that the underlining handler should run on
+ * @hide
+ */
+ public OnSubscriptionsChangedListener(Looper looper) {
+ mHandler = new OnSubscriptionsChangedListenerHandler(looper);
+ }
/**
* Callback invoked when there is any change to any SubscriptionInfo. Typically
diff --git a/android/telephony/TelephonyManager.java b/android/telephony/TelephonyManager.java
index 4ffb3c32..81806e52 100644
--- a/android/telephony/TelephonyManager.java
+++ b/android/telephony/TelephonyManager.java
@@ -53,8 +53,9 @@ import android.telephony.VisualVoicemailService.VisualVoicemailTask;
import android.telephony.ims.feature.ImsFeature;
import android.util.Log;
-import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.ITelecomService;
import com.android.internal.telephony.CellNetworkScanResult;
@@ -408,9 +409,12 @@ public class TelephonyManager {
* Open the voicemail settings activity to make changes to voicemail configuration.
*
* <p>
+ * The {@link #EXTRA_PHONE_ACCOUNT_HANDLE} extra indicates which {@link PhoneAccountHandle} to
+ * configure voicemail.
* The {@link #EXTRA_HIDE_PUBLIC_SETTINGS} hides settings the dialer will modify through public
* API if set.
*
+ * @see #EXTRA_PHONE_ACCOUNT_HANDLE
* @see #EXTRA_HIDE_PUBLIC_SETTINGS
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -775,8 +779,9 @@ public class TelephonyManager {
"android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
/**
- * The extra used with an {@link #ACTION_SHOW_VOICEMAIL_NOTIFICATION} {@code Intent} to specify
- * the {@link PhoneAccountHandle} the notification is for.
+ * The extra used with an {@link #ACTION_CONFIGURE_VOICEMAIL} and
+ * {@link #ACTION_SHOW_VOICEMAIL_NOTIFICATION} {@code Intent} to specify the
+ * {@link PhoneAccountHandle} the configuration or notification is for.
* <p class="note">
* Retrieve with {@link android.content.Intent#getParcelableExtra(String)}.
*/
@@ -4659,27 +4664,78 @@ public class TelephonyManager {
public @interface Feature {}
/**
- * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS
- * feature or {@link null} if the service is not available. If an ImsServiceController is
- * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for
- * feature updates.
- * @param slotIndex The SIM slot that we are requesting the {@link IImsServiceController} for.
- * @param feature The IMS Feature we are requesting, corresponding to {@link ImsFeature}.
+ * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id and MMTel
+ * feature or {@link null} if the service is not available. If an MMTelFeature is available, the
+ * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates.
+ * @param slotIndex The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
+ * @param callback Listener that will send updates to ImsManager when there are updates to
+ * ImsServiceController.
+ * @return {@link IImsMMTelFeature} interface for the feature specified or {@code null} if
+ * it is unavailable.
+ * @hide
+ */
+ public @Nullable IImsMMTelFeature getImsMMTelFeatureAndListen(int slotIndex,
+ IImsServiceFeatureCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getMMTelFeatureAndListen(slotIndex, callback);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "getImsMMTelFeatureAndListen, RemoteException: "
+ + e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id and MMTel
+ * feature for emergency calling or {@link null} if the service is not available. If an
+ * MMTelFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a
+ * listener for feature updates.
+ * @param slotIndex The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
+ * @param callback Listener that will send updates to ImsManager when there are updates to
+ * ImsServiceController.
+ * @return {@link IImsMMTelFeature} interface for the feature specified or {@code null} if
+ * it is unavailable.
+ * @hide
+ */
+ public @Nullable IImsMMTelFeature getImsEmergencyMMTelFeatureAndListen(int slotIndex,
+ IImsServiceFeatureCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getEmergencyMMTelFeatureAndListen(slotIndex, callback);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "getImsEmergencyMMTelFeatureAndListen, RemoteException: "
+ + e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link IImsRcsFeature} that corresponds to the given slot Id and RCS
+ * feature for emergency calling or {@link null} if the service is not available. If an
+ * RcsFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a
+ * listener for feature updates.
+ * @param slotIndex The SIM slot that we are requesting the {@link IImsRcsFeature} for.
* @param callback Listener that will send updates to ImsManager when there are updates to
* ImsServiceController.
- * @return {@link IImsServiceController} interface for the feature specified or {@link null} if
+ * @return {@link IImsRcsFeature} interface for the feature specified or {@code null} if
* it is unavailable.
* @hide
*/
- public IImsServiceController getImsServiceControllerAndListen(int slotIndex, @Feature int feature,
- IImsServiceFeatureListener callback) {
+ public @Nullable IImsRcsFeature getImsRcsFeatureAndListen(int slotIndex,
+ IImsServiceFeatureCallback callback) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getImsServiceControllerAndListen(slotIndex, feature, callback);
+ return telephony.getRcsFeatureAndListen(slotIndex, callback);
}
} catch (RemoteException e) {
- Rlog.e(TAG, "getImsServiceControllerAndListen, RemoteException: " + e.getMessage());
+ Rlog.e(TAG, "getImsRcsFeatureAndListen, RemoteException: "
+ + e.getMessage());
}
return null;
}
diff --git a/android/telephony/data/DataProfile.java b/android/telephony/data/DataProfile.java
new file mode 100644
index 00000000..41c1430e
--- /dev/null
+++ b/android/telephony/data/DataProfile.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.data;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.RILConstants;
+
+/**
+ * Description of a mobile data profile used for establishing
+ * data connections.
+ *
+ * @hide
+ */
+@SystemApi
+public final class DataProfile implements Parcelable {
+
+ // The types indicating the data profile is used on GSM (3GPP) or CDMA (3GPP2) network.
+ public static final int TYPE_COMMON = 0;
+ public static final int TYPE_3GPP = 1;
+ public static final int TYPE_3GPP2 = 2;
+
+ private final int mProfileId;
+
+ private final String mApn;
+
+ private final String mProtocol;
+
+ private final int mAuthType;
+
+ private final String mUserName;
+
+ private final String mPassword;
+
+ private final int mType;
+
+ private final int mMaxConnsTime;
+
+ private final int mMaxConns;
+
+ private final int mWaitTime;
+
+ private final boolean mEnabled;
+
+ private final int mSupportedApnTypesBitmap;
+
+ private final String mRoamingProtocol;
+
+ private final int mBearerBitmap;
+
+ private final int mMtu;
+
+ private final String mMvnoType;
+
+ private final String mMvnoMatchData;
+
+ private final boolean mModemCognitive;
+
+ public DataProfile(int profileId, String apn, String protocol, int authType,
+ String userName, String password, int type, int maxConnsTime, int maxConns,
+ int waitTime, boolean enabled, int supportedApnTypesBitmap, String roamingProtocol,
+ int bearerBitmap, int mtu, String mvnoType, String mvnoMatchData,
+ boolean modemCognitive) {
+
+ this.mProfileId = profileId;
+ this.mApn = apn;
+ this.mProtocol = protocol;
+ if (authType == -1) {
+ authType = TextUtils.isEmpty(userName) ? RILConstants.SETUP_DATA_AUTH_NONE
+ : RILConstants.SETUP_DATA_AUTH_PAP_CHAP;
+ }
+ this.mAuthType = authType;
+ this.mUserName = userName;
+ this.mPassword = password;
+ this.mType = type;
+ this.mMaxConnsTime = maxConnsTime;
+ this.mMaxConns = maxConns;
+ this.mWaitTime = waitTime;
+ this.mEnabled = enabled;
+
+ this.mSupportedApnTypesBitmap = supportedApnTypesBitmap;
+ this.mRoamingProtocol = roamingProtocol;
+ this.mBearerBitmap = bearerBitmap;
+ this.mMtu = mtu;
+ this.mMvnoType = mvnoType;
+ this.mMvnoMatchData = mvnoMatchData;
+ this.mModemCognitive = modemCognitive;
+ }
+
+ public DataProfile(Parcel source) {
+ mProfileId = source.readInt();
+ mApn = source.readString();
+ mProtocol = source.readString();
+ mAuthType = source.readInt();
+ mUserName = source.readString();
+ mPassword = source.readString();
+ mType = source.readInt();
+ mMaxConnsTime = source.readInt();
+ mMaxConns = source.readInt();
+ mWaitTime = source.readInt();
+ mEnabled = source.readBoolean();
+ mSupportedApnTypesBitmap = source.readInt();
+ mRoamingProtocol = source.readString();
+ mBearerBitmap = source.readInt();
+ mMtu = source.readInt();
+ mMvnoType = source.readString();
+ mMvnoMatchData = source.readString();
+ mModemCognitive = source.readBoolean();
+ }
+
+ /**
+ * @return Id of the data profile.
+ */
+ public int getProfileId() { return mProfileId; }
+
+ /**
+ * @return The APN to establish data connection.
+ */
+ public String getApn() { return mApn; }
+
+ /**
+ * @return The connection protocol, should be one of the PDP_type values in TS 27.007 section
+ * 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP".
+ */
+ public String getProtocol() { return mProtocol; }
+
+ /**
+ * @return The authentication protocol used for this PDP context
+ * (None: 0, PAP: 1, CHAP: 2, PAP&CHAP: 3)
+ */
+ public int getAuthType() { return mAuthType; }
+
+ /**
+ * @return The username for APN. Can be null.
+ */
+ public String getUserName() { return mUserName; }
+
+ /**
+ * @return The password for APN. Can be null.
+ */
+ public String getPassword() { return mPassword; }
+
+ /**
+ * @return The profile type. Could be one of TYPE_COMMON, TYPE_3GPP, or TYPE_3GPP2.
+ */
+ public int getType() { return mType; }
+
+ /**
+ * @return The period in seconds to limit the maximum connections.
+ */
+ public int getMaxConnsTime() { return mMaxConnsTime; }
+
+ /**
+ * @return The maximum connections allowed.
+ */
+ public int getMaxConns() { return mMaxConns; }
+
+ /**
+ * @return The required wait time in seconds after a successful UE initiated disconnect of a
+ * given PDN connection before the device can send a new PDN connection request for that given
+ * PDN.
+ */
+ public int getWaitTime() { return mWaitTime; }
+
+ /**
+ * @return True if the profile is enabled.
+ */
+ public boolean isEnabled() { return mEnabled; }
+
+ /**
+ * @return The supported APN types bitmap. See RIL_ApnTypes for the value of each bit.
+ */
+ public int getSupportedApnTypesBitmap() { return mSupportedApnTypesBitmap; }
+
+ /**
+ * @return The connection protocol on roaming network, should be one of the PDP_type values in
+ * TS 27.007 section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP".
+ */
+ public String getRoamingProtocol() { return mRoamingProtocol; }
+
+ /**
+ * @return The bearer bitmap. See RIL_RadioAccessFamily for the value of each bit.
+ */
+ public int getBearerBitmap() { return mBearerBitmap; }
+
+ /**
+ * @return The maximum transmission unit (MTU) size in bytes.
+ */
+ public int getMtu() { return mMtu; }
+
+ /**
+ * @return The MVNO type: possible values are "imsi", "gid", "spn".
+ */
+ public String getMvnoType() { return mMvnoType; }
+
+ /**
+ * @return The MVNO match data. For example,
+ * SPN: A MOBILE, BEN NL, ...
+ * IMSI: 302720x94, 2060188, ...
+ * GID: 4E, 33, ...
+ */
+ public String getMvnoMatchData() { return mMvnoMatchData; }
+
+ /**
+ * @return True if the data profile was sent to the modem through setDataProfile earlier.
+ */
+ public boolean isModemCognitive() { return mModemCognitive; }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "DataProfile=" + mProfileId + "/" + mApn + "/" + mProtocol + "/" + mAuthType
+ + "/" + mUserName + "/" + mPassword + "/" + mType + "/" + mMaxConnsTime
+ + "/" + mMaxConns + "/" + mWaitTime + "/" + mEnabled + "/"
+ + mSupportedApnTypesBitmap + "/" + mRoamingProtocol + "/" + mBearerBitmap + "/"
+ + mMtu + "/" + mMvnoType + "/" + mMvnoMatchData + "/" + mModemCognitive;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof DataProfile == false) return false;
+ return (o == this || toString().equals(o.toString()));
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mProfileId);
+ dest.writeString(mApn);
+ dest.writeString(mProtocol);
+ dest.writeInt(mAuthType);
+ dest.writeString(mUserName);
+ dest.writeString(mPassword);
+ dest.writeInt(mType);
+ dest.writeInt(mMaxConnsTime);
+ dest.writeInt(mMaxConns);
+ dest.writeInt(mWaitTime);
+ dest.writeBoolean(mEnabled);
+ dest.writeInt(mSupportedApnTypesBitmap);
+ dest.writeString(mRoamingProtocol);
+ dest.writeInt(mBearerBitmap);
+ dest.writeInt(mMtu);
+ dest.writeString(mMvnoType);
+ dest.writeString(mMvnoMatchData);
+ dest.writeBoolean(mModemCognitive);
+ }
+
+ public static final Parcelable.Creator<DataProfile> CREATOR =
+ new Parcelable.Creator<DataProfile>() {
+ @Override
+ public DataProfile createFromParcel(Parcel source) {
+ return new DataProfile(source);
+ }
+
+ @Override
+ public DataProfile[] newArray(int size) {
+ return new DataProfile[size];
+ }
+ };
+}
diff --git a/android/telephony/ims/ImsService.java b/android/telephony/ims/ImsService.java
index 9d91cc33..8230eafc 100644
--- a/android/telephony/ims/ImsService.java
+++ b/android/telephony/ims/ImsService.java
@@ -16,13 +16,11 @@
package android.telephony.ims;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.os.IBinder;
-import android.os.Message;
import android.os.RemoteException;
import android.telephony.CarrierConfigManager;
import android.telephony.ims.feature.ImsFeature;
@@ -31,22 +29,13 @@ import android.telephony.ims.feature.RcsFeature;
import android.util.Log;
import android.util.SparseArray;
-import com.android.ims.ImsCallProfile;
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsCallSessionListener;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
import com.android.ims.internal.IImsFeatureStatusCallback;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
-import com.android.ims.internal.IImsUt;
import com.android.internal.annotations.VisibleForTesting;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
-import static android.Manifest.permission.READ_PHONE_STATE;
-import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
/**
* Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend
@@ -92,247 +81,38 @@ public class ImsService extends Service {
*/
public static final String SERVICE_INTERFACE = "android.telephony.ims.ImsService";
- // A map of slot Id -> Set of features corresponding to that slot.
- private final SparseArray<SparseArray<ImsFeature>> mFeatures = new SparseArray<>();
+ // A map of slot Id -> map of features (indexed by ImsFeature feature id) corresponding to that
+ // slot.
+ // We keep track of this to facilitate cleanup of the IImsFeatureStatusCallback and
+ // call ImsFeature#onFeatureRemoved.
+ private final SparseArray<SparseArray<ImsFeature>> mFeaturesBySlot = new SparseArray<>();
/**
* @hide
*/
- // Implements all supported features as a flat interface.
protected final IBinder mImsServiceController = new IImsServiceController.Stub() {
@Override
- public void createImsFeature(int slotId, int feature, IImsFeatureStatusCallback c)
- throws RemoteException {
- synchronized (mFeatures) {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createImsFeature");
- onCreateImsFeatureInternal(slotId, feature, c);
- }
+ public IImsMMTelFeature createEmergencyMMTelFeature(int slotId,
+ IImsFeatureStatusCallback c) {
+ return createEmergencyMMTelFeatureInternal(slotId, c);
}
@Override
- public void removeImsFeature(int slotId, int feature, IImsFeatureStatusCallback c)
- throws RemoteException {
- synchronized (mFeatures) {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "removeImsFeature");
- onRemoveImsFeatureInternal(slotId, feature, c);
- }
+ public IImsMMTelFeature createMMTelFeature(int slotId, IImsFeatureStatusCallback c) {
+ return createMMTelFeatureInternal(slotId, c);
}
@Override
- public int startSession(int slotId, int featureType, PendingIntent incomingCallIntent,
- IImsRegistrationListener listener) throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "startSession");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.startSession(incomingCallIntent, listener);
- }
- }
- return 0;
+ public IImsRcsFeature createRcsFeature(int slotId, IImsFeatureStatusCallback c) {
+ return createRcsFeatureInternal(slotId, c);
}
@Override
- public void endSession(int slotId, int featureType, int sessionId) throws RemoteException {
- synchronized (mFeatures) {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "endSession");
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- feature.endSession(sessionId);
- }
- }
- }
-
- @Override
- public boolean isConnected(int slotId, int featureType, int callSessionType, int callType)
+ public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
throws RemoteException {
- enforceReadPhoneStatePermission("isConnected");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.isConnected(callSessionType, callType);
- }
- }
- return false;
- }
-
- @Override
- public boolean isOpened(int slotId, int featureType) throws RemoteException {
- enforceReadPhoneStatePermission("isOpened");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.isOpened();
- }
- }
- return false;
- }
-
- @Override
- public int getFeatureStatus(int slotId, int featureType) throws RemoteException {
- enforceReadPhoneStatePermission("getFeatureStatus");
- int status = ImsFeature.STATE_NOT_AVAILABLE;
- synchronized (mFeatures) {
- SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
- if (featureMap != null) {
- ImsFeature feature = getImsFeatureFromType(featureMap, featureType);
- if (feature != null) {
- status = feature.getFeatureState();
- }
- }
- }
- return status;
- }
-
- @Override
- public void addRegistrationListener(int slotId, int featureType,
- IImsRegistrationListener listener) throws RemoteException {
- enforceReadPhoneStatePermission("addRegistrationListener");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- feature.addRegistrationListener(listener);
- }
- }
+ ImsService.this.removeImsFeature(slotId, featureType, c);
}
-
- @Override
- public void removeRegistrationListener(int slotId, int featureType,
- IImsRegistrationListener listener) throws RemoteException {
- enforceReadPhoneStatePermission("removeRegistrationListener");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- feature.removeRegistrationListener(listener);
- }
- }
- }
-
- @Override
- public ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId,
- int callSessionType, int callType) throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createCallProfile");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.createCallProfile(sessionId, callSessionType, callType);
- }
- }
- return null;
- }
-
- @Override
- public IImsCallSession createCallSession(int slotId, int featureType, int sessionId,
- ImsCallProfile profile, IImsCallSessionListener listener) throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createCallSession");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.createCallSession(sessionId, profile, listener);
- }
- }
- return null;
- }
-
- @Override
- public IImsCallSession getPendingCallSession(int slotId, int featureType, int sessionId,
- String callId) throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getPendingCallSession");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.getPendingCallSession(sessionId, callId);
- }
- }
- return null;
- }
-
- @Override
- public IImsUt getUtInterface(int slotId, int featureType)
- throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getUtInterface");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.getUtInterface();
- }
- }
- return null;
- }
-
- @Override
- public IImsConfig getConfigInterface(int slotId, int featureType)
- throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getConfigInterface");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.getConfigInterface();
- }
- }
- return null;
- }
-
- @Override
- public void turnOnIms(int slotId, int featureType) throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "turnOnIms");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- feature.turnOnIms();
- }
- }
- }
-
- @Override
- public void turnOffIms(int slotId, int featureType) throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "turnOffIms");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- feature.turnOffIms();
- }
- }
- }
-
- @Override
- public IImsEcbm getEcbmInterface(int slotId, int featureType)
- throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getEcbmInterface");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.getEcbmInterface();
- }
- }
- return null;
- }
-
- @Override
- public void setUiTTYMode(int slotId, int featureType, int uiTtyMode, Message onComplete)
- throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "setUiTTYMode");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- feature.setUiTTYMode(uiTtyMode, onComplete);
- }
- }
- }
-
- @Override
- public IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType)
- throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getMultiEndpointInterface");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.getMultiEndpointInterface();
- }
- }
- return null;
- }
-
};
/**
@@ -341,127 +121,93 @@ public class ImsService extends Service {
@Override
public IBinder onBind(Intent intent) {
if(SERVICE_INTERFACE.equals(intent.getAction())) {
+ Log.i(LOG_TAG, "ImsService Bound.");
return mImsServiceController;
}
return null;
}
/**
- * Called from the ImsResolver to create the requested ImsFeature, as defined by the slot and
- * featureType
- * @param slotId An integer representing which SIM slot the ImsFeature is assigned to.
- * @param featureType An integer representing the type of ImsFeature being created. This is
- * defined in {@link ImsFeature}.
+ * @hide
*/
- // Be sure to lock on mFeatures before accessing this method
- private void onCreateImsFeatureInternal(int slotId, int featureType,
- IImsFeatureStatusCallback c) {
- SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
- if (featureMap == null) {
- featureMap = new SparseArray<>();
- mFeatures.put(slotId, featureMap);
- }
- ImsFeature f = makeImsFeature(slotId, featureType);
- if (f != null) {
- f.setContext(this);
- f.setSlotId(slotId);
- f.addImsFeatureStatusCallback(c);
- featureMap.put(featureType, f);
- }
-
+ @VisibleForTesting
+ public SparseArray<ImsFeature> getFeatures(int slotId) {
+ return mFeaturesBySlot.get(slotId);
}
- /**
- * Called from the ImsResolver to remove an existing ImsFeature, as defined by the slot and
- * featureType.
- * @param slotId An integer representing which SIM slot the ImsFeature is assigned to.
- * @param featureType An integer representing the type of ImsFeature being removed. This is
- * defined in {@link ImsFeature}.
- */
- // Be sure to lock on mFeatures before accessing this method
- private void onRemoveImsFeatureInternal(int slotId, int featureType,
- IImsFeatureStatusCallback c) {
- SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
- if (featureMap == null) {
- return;
- }
- ImsFeature featureToRemove = getImsFeatureFromType(featureMap, featureType);
- if (featureToRemove != null) {
- featureMap.remove(featureType);
- featureToRemove.notifyFeatureRemoved(slotId);
- // Remove reference to Binder
- featureToRemove.removeImsFeatureStatusCallback(c);
+ private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId,
+ IImsFeatureStatusCallback c) {
+ MMTelFeature f = onCreateEmergencyMMTelImsFeature(slotId);
+ if (f != null) {
+ setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL, c);
+ return f.getBinder();
+ } else {
+ return null;
}
}
- // Be sure to lock on mFeatures before accessing this method
- private MMTelFeature resolveMMTelFeature(int slotId, int featureType) {
- SparseArray<ImsFeature> features = getImsFeatureMap(slotId);
- MMTelFeature feature = null;
- if (features != null) {
- feature = resolveImsFeature(features, featureType, MMTelFeature.class);
+ private IImsMMTelFeature createMMTelFeatureInternal(int slotId,
+ IImsFeatureStatusCallback c) {
+ MMTelFeature f = onCreateMMTelImsFeature(slotId);
+ if (f != null) {
+ setupFeature(f, slotId, ImsFeature.MMTEL, c);
+ return f.getBinder();
+ } else {
+ return null;
}
- return feature;
}
- // Be sure to lock on mFeatures before accessing this method
- private <T extends ImsFeature> T resolveImsFeature(SparseArray<ImsFeature> set, int featureType,
- Class<T> className) {
- ImsFeature feature = getImsFeatureFromType(set, featureType);
- if (feature == null) {
+ private IImsRcsFeature createRcsFeatureInternal(int slotId,
+ IImsFeatureStatusCallback c) {
+ RcsFeature f = onCreateRcsFeature(slotId);
+ if (f != null) {
+ setupFeature(f, slotId, ImsFeature.RCS, c);
+ return f.getBinder();
+ } else {
return null;
}
- try {
- return className.cast(feature);
- } catch (ClassCastException e)
- {
- Log.e(LOG_TAG, "Can not cast ImsFeature! Exception: " + e.getMessage());
- }
- return null;
}
- /**
- * @hide
- */
- @VisibleForTesting
- // Be sure to lock on mFeatures before accessing this method
- public SparseArray<ImsFeature> getImsFeatureMap(int slotId) {
- return mFeatures.get(slotId);
- }
-
- /**
- * @hide
- */
- @VisibleForTesting
- // Be sure to lock on mFeatures before accessing this method
- public ImsFeature getImsFeatureFromType(SparseArray<ImsFeature> set, int featureType) {
- return set.get(featureType);
+ private void setupFeature(ImsFeature f, int slotId, int featureType,
+ IImsFeatureStatusCallback c) {
+ f.setContext(this);
+ f.setSlotId(slotId);
+ f.addImsFeatureStatusCallback(c);
+ addImsFeature(slotId, featureType, f);
}
- private ImsFeature makeImsFeature(int slotId, int feature) {
- switch (feature) {
- case ImsFeature.EMERGENCY_MMTEL: {
- return onCreateEmergencyMMTelImsFeature(slotId);
- }
- case ImsFeature.MMTEL: {
- return onCreateMMTelImsFeature(slotId);
- }
- case ImsFeature.RCS: {
- return onCreateRcsFeature(slotId);
+ private void addImsFeature(int slotId, int featureType, ImsFeature f) {
+ synchronized (mFeaturesBySlot) {
+ // Get SparseArray for Features, by querying slot Id
+ SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+ if (features == null) {
+ // Populate new SparseArray of features if it doesn't exist for this slot yet.
+ features = new SparseArray<>();
+ mFeaturesBySlot.put(slotId, features);
}
+ features.put(featureType, f);
}
- // Tried to create feature that is not defined.
- return null;
}
- /**
- * Check for both READ_PHONE_STATE and READ_PRIVILEGED_PHONE_STATE. READ_PHONE_STATE is a
- * public permission and READ_PRIVILEGED_PHONE_STATE is only granted to system apps.
- */
- private void enforceReadPhoneStatePermission(String fn) {
- if (checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE)
- != PackageManager.PERMISSION_GRANTED) {
- enforceCallingOrSelfPermission(READ_PHONE_STATE, fn);
+ private void removeImsFeature(int slotId, int featureType,
+ IImsFeatureStatusCallback c) {
+ synchronized (mFeaturesBySlot) {
+ // get ImsFeature associated with the slot/feature
+ SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+ if (features == null) {
+ Log.w(LOG_TAG, "Can not remove ImsFeature. No ImsFeatures exist on slot "
+ + slotId);
+ return;
+ }
+ ImsFeature f = features.get(featureType);
+ if (f == null) {
+ Log.w(LOG_TAG, "Can not remove ImsFeature. No feature with type "
+ + featureType + " exists on slot " + slotId);
+ return;
+ }
+ f.removeImsFeatureStatusCallback(c);
+ f.onFeatureRemoved();
+ features.remove(featureType);
}
}
@@ -470,7 +216,7 @@ public class ImsService extends Service {
* functionality. Must be able to handle emergency calls at any time as well.
* @hide
*/
- public MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) {
+ public @Nullable MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) {
return null;
}
@@ -479,7 +225,7 @@ public class ImsService extends Service {
* functionality.
* @hide
*/
- public MMTelFeature onCreateMMTelImsFeature(int slotId) {
+ public @Nullable MMTelFeature onCreateMMTelImsFeature(int slotId) {
return null;
}
@@ -487,7 +233,7 @@ public class ImsService extends Service {
* @return An implementation of RcsFeature that will be used by the system for RCS.
* @hide
*/
- public RcsFeature onCreateRcsFeature(int slotId) {
+ public @Nullable RcsFeature onCreateRcsFeature(int slotId) {
return null;
}
}
diff --git a/android/telephony/ims/feature/ImsFeature.java b/android/telephony/ims/feature/ImsFeature.java
index 9d880b73..062858d4 100644
--- a/android/telephony/ims/feature/ImsFeature.java
+++ b/android/telephony/ims/feature/ImsFeature.java
@@ -19,6 +19,7 @@ package android.telephony.ims.feature;
import android.annotation.IntDef;
import android.content.Context;
import android.content.Intent;
+import android.os.IInterface;
import android.os.RemoteException;
import android.telephony.SubscriptionManager;
import android.util.Log;
@@ -91,17 +92,12 @@ public abstract class ImsFeature {
public static final int STATE_INITIALIZING = 1;
public static final int STATE_READY = 2;
- private List<INotifyFeatureRemoved> mRemovedListeners = new ArrayList<>();
private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap(
new WeakHashMap<IImsFeatureStatusCallback, Boolean>());
private @ImsState int mState = STATE_NOT_AVAILABLE;
private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
private Context mContext;
- public interface INotifyFeatureRemoved {
- void onFeatureRemoved(int slotId);
- }
-
public void setContext(Context context) {
mContext = context;
}
@@ -110,26 +106,6 @@ public abstract class ImsFeature {
mSlotId = slotId;
}
- public void addFeatureRemovedListener(INotifyFeatureRemoved listener) {
- synchronized (mRemovedListeners) {
- mRemovedListeners.add(listener);
- }
- }
-
- public void removeFeatureRemovedListener(INotifyFeatureRemoved listener) {
- synchronized (mRemovedListeners) {
- mRemovedListeners.remove(listener);
- }
- }
-
- // Not final for testing.
- public void notifyFeatureRemoved(int slotId) {
- synchronized (mRemovedListeners) {
- mRemovedListeners.forEach(l -> l.onFeatureRemoved(slotId));
- onFeatureRemoved();
- }
- }
-
public int getFeatureState() {
return mState;
}
@@ -215,4 +191,9 @@ public abstract class ImsFeature {
* Called when the feature is being removed and must be cleaned up.
*/
public abstract void onFeatureRemoved();
+
+ /**
+ * @return Binder instance
+ */
+ public abstract IInterface getBinder();
}
diff --git a/android/telephony/ims/feature/MMTelFeature.java b/android/telephony/ims/feature/MMTelFeature.java
index 758c379f..e790d146 100644
--- a/android/telephony/ims/feature/MMTelFeature.java
+++ b/android/telephony/ims/feature/MMTelFeature.java
@@ -18,18 +18,18 @@ package android.telephony.ims.feature;
import android.app.PendingIntent;
import android.os.Message;
+import android.os.RemoteException;
import com.android.ims.ImsCallProfile;
import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsCallSessionListener;
import com.android.ims.internal.IImsConfig;
import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMMTelFeature;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsRegistrationListener;
import com.android.ims.internal.IImsUt;
-
-import java.util.ArrayList;
-import java.util.List;
+import com.android.ims.internal.ImsCallSession;
/**
* Base implementation for MMTel.
@@ -41,6 +41,146 @@ import java.util.List;
public class MMTelFeature extends ImsFeature {
+ // Lock for feature synchronization
+ private final Object mLock = new Object();
+
+ private final IImsMMTelFeature mImsMMTelBinder = new IImsMMTelFeature.Stub() {
+
+ @Override
+ public int startSession(PendingIntent incomingCallIntent,
+ IImsRegistrationListener listener) throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.startSession(incomingCallIntent, listener);
+ }
+ }
+
+ @Override
+ public void endSession(int sessionId) throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.endSession(sessionId);
+ }
+ }
+
+ @Override
+ public boolean isConnected(int callSessionType, int callType)
+ throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.isConnected(callSessionType, callType);
+ }
+ }
+
+ @Override
+ public boolean isOpened() throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.isOpened();
+ }
+ }
+
+ @Override
+ public int getFeatureStatus() throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.getFeatureState();
+ }
+ }
+
+ @Override
+ public void addRegistrationListener(IImsRegistrationListener listener)
+ throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.addRegistrationListener(listener);
+ }
+ }
+
+ @Override
+ public void removeRegistrationListener(IImsRegistrationListener listener)
+ throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.removeRegistrationListener(listener);
+ }
+ }
+
+ @Override
+ public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType)
+ throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.createCallProfile(sessionId, callSessionType, callType);
+ }
+ }
+
+ @Override
+ public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
+ IImsCallSessionListener listener) throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.createCallSession(sessionId, profile, listener);
+ }
+ }
+
+ @Override
+ public IImsCallSession getPendingCallSession(int sessionId, String callId)
+ throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.getPendingCallSession(sessionId, callId);
+ }
+ }
+
+ @Override
+ public IImsUt getUtInterface() throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.getUtInterface();
+ }
+ }
+
+ @Override
+ public IImsConfig getConfigInterface() throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.getConfigInterface();
+ }
+ }
+
+ @Override
+ public void turnOnIms() throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.turnOnIms();
+ }
+ }
+
+ @Override
+ public void turnOffIms() throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.turnOffIms();
+ }
+ }
+
+ @Override
+ public IImsEcbm getEcbmInterface() throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.getEcbmInterface();
+ }
+ }
+
+ @Override
+ public void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.setUiTTYMode(uiTtyMode, onComplete);
+ }
+ }
+
+ @Override
+ public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.getMultiEndpointInterface();
+ }
+ }
+ };
+
+ /**
+ * @hide
+ */
+ @Override
+ public final IImsMMTelFeature getBinder() {
+ return mImsMMTelBinder;
+ }
+
/**
* Notifies the MMTel feature that you would like to start a session. This should always be
* done before making/receiving IMS calls. The IMS service will register the device to the
@@ -135,7 +275,7 @@ public class MMTelFeature extends ImsFeature {
}
/**
- * Creates a {@link ImsCallSession} with the specified call profile.
+ * Creates an {@link ImsCallSession} with the specified call profile.
* Use other methods, if applicable, instead of interacting with
* {@link ImsCallSession} directly.
*
diff --git a/android/telephony/ims/feature/RcsFeature.java b/android/telephony/ims/feature/RcsFeature.java
index 332cca3e..a82e6086 100644
--- a/android/telephony/ims/feature/RcsFeature.java
+++ b/android/telephony/ims/feature/RcsFeature.java
@@ -16,6 +16,8 @@
package android.telephony.ims.feature;
+import com.android.ims.internal.IImsRcsFeature;
+
/**
* Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend
* this class and provide implementations of the RcsFeature methods that they support.
@@ -24,6 +26,11 @@ package android.telephony.ims.feature;
public class RcsFeature extends ImsFeature {
+ private final IImsRcsFeature mImsRcsBinder = new IImsRcsFeature.Stub() {
+ // Empty Default Implementation.
+ };
+
+
public RcsFeature() {
super();
}
@@ -32,4 +39,9 @@ public class RcsFeature extends ImsFeature {
public void onFeatureRemoved() {
}
+
+ @Override
+ public final IImsRcsFeature getBinder() {
+ return mImsRcsBinder;
+ }
}
diff --git a/android/test/mock/MockPackageManager.java b/android/test/mock/MockPackageManager.java
index 7e08f51c..0c562e65 100644
--- a/android/test/mock/MockPackageManager.java
+++ b/android/test/mock/MockPackageManager.java
@@ -26,12 +26,11 @@ import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ChangedPackages;
-import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstantAppInfo;
import android.content.pm.InstrumentationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
@@ -653,15 +652,6 @@ public class MockPackageManager extends PackageManager {
throw new UnsupportedOperationException();
}
- /**
- * @hide - to match hiding in superclass
- */
- @Override
- public void installPackage(Uri packageURI, IPackageInstallObserver observer,
- int flags, String installerPackageName) {
- throw new UnsupportedOperationException();
- }
-
@Override
public void setInstallerPackageName(String targetPackage,
String installerPackageName) {
diff --git a/android/test/suitebuilder/TestSuiteBuilder.java b/android/test/suitebuilder/TestSuiteBuilder.java
index 6158e0cf..2857696e 100644
--- a/android/test/suitebuilder/TestSuiteBuilder.java
+++ b/android/test/suitebuilder/TestSuiteBuilder.java
@@ -119,6 +119,7 @@ public class TestSuiteBuilder {
*
* @param predicates Predicates to add to the list of requirements.
* @return The builder for method chaining.
+ * @hide
*/
public TestSuiteBuilder addRequirements(List<Predicate<TestMethod>> predicates) {
this.predicates.addAll(predicates);
@@ -156,7 +157,7 @@ public class TestSuiteBuilder {
/**
* Override the default name for the suite being built. This should generally be called if you
- * call {@link #addRequirements(com.android.internal.util.Predicate[])} to make it clear which
+ * call {@code addRequirements(com.android.internal.util.Predicate[])} to make it clear which
* tests will be included. The name you specify is automatically prefixed with the package
* containing the tests to be run. If more than one package is specified, the first is used.
*
@@ -215,6 +216,7 @@ public class TestSuiteBuilder {
*
* @param predicates Predicates to add to the list of requirements.
* @return The builder for method chaining.
+ * @hide
*/
public final TestSuiteBuilder addRequirements(Predicate<TestMethod>... predicates) {
ArrayList<Predicate<TestMethod>> list = new ArrayList<Predicate<TestMethod>>();
diff --git a/android/text/StaticLayout_Delegate.java b/android/text/StaticLayout_Delegate.java
index ef7525ec..ca8743c7 100644
--- a/android/text/StaticLayout_Delegate.java
+++ b/android/text/StaticLayout_Delegate.java
@@ -15,6 +15,7 @@ import android.text.Layout.HyphenationFrequency;
import android.text.Primitive.PrimitiveType;
import android.text.StaticLayout.LineBreaks;
+import java.text.CharacterIterator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -116,7 +117,7 @@ public class StaticLayout_Delegate {
// compute all possible breakpoints.
BreakIterator it = BreakIterator.getLineInstance();
- it.setText(new Segment(builder.mText, 0, length));
+ it.setText((CharacterIterator) new Segment(builder.mText, 0, length));
// average word length in english is 5. So, initialize the possible breaks with a guess.
List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d));
diff --git a/android/text/format/Formatter.java b/android/text/format/Formatter.java
index 2c83fc4d..8c90156d 100644
--- a/android/text/format/Formatter.java
+++ b/android/text/format/Formatter.java
@@ -355,21 +355,21 @@ public final class Formatter {
final Locale locale = localeFromContext(context);
final MeasureFormat measureFormat = MeasureFormat.getInstance(
locale, MeasureFormat.FormatWidth.SHORT);
- if (days >= 2) {
+ if (days >= 2 || (days > 0 && hours == 0)) {
days += (hours+12)/24;
return measureFormat.format(new Measure(days, MeasureUnit.DAY));
} else if (days > 0) {
return measureFormat.formatMeasures(
new Measure(days, MeasureUnit.DAY),
new Measure(hours, MeasureUnit.HOUR));
- } else if (hours >= 2) {
+ } else if (hours >= 2 || (hours > 0 && minutes == 0)) {
hours += (minutes+30)/60;
return measureFormat.format(new Measure(hours, MeasureUnit.HOUR));
} else if (hours > 0) {
return measureFormat.formatMeasures(
new Measure(hours, MeasureUnit.HOUR),
new Measure(minutes, MeasureUnit.MINUTE));
- } else if (minutes >= 2) {
+ } else if (minutes >= 2 || (minutes > 0 && seconds == 0)) {
minutes += (seconds+30)/60;
return measureFormat.format(new Measure(minutes, MeasureUnit.MINUTE));
} else if (minutes > 0) {
diff --git a/android/util/AndroidException.java b/android/util/AndroidException.java
index dfe00c9b..1345ddf1 100644
--- a/android/util/AndroidException.java
+++ b/android/util/AndroidException.java
@@ -34,5 +34,11 @@ public class AndroidException extends Exception {
public AndroidException(Exception cause) {
super(cause);
}
+
+ /** @hide */
+ protected AndroidException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
};
diff --git a/android/util/FeatureFlagUtils.java b/android/util/FeatureFlagUtils.java
index 2a272208..29baea17 100644
--- a/android/util/FeatureFlagUtils.java
+++ b/android/util/FeatureFlagUtils.java
@@ -18,6 +18,7 @@ package android.util;
import android.content.Context;
import android.os.SystemProperties;
+import android.provider.Settings;
import android.text.TextUtils;
import java.util.Map;
@@ -39,13 +40,24 @@ public class FeatureFlagUtils {
* @return true if the flag is enabled (either by default in system, or override by user)
*/
public static boolean isEnabled(Context context, String feature) {
- // Tries to get feature flag from system property.
- // Step 1: check if feature flag has any override. Flag name: sys.fflag.override.<feature>
- String value = SystemProperties.get(FFLAG_OVERRIDE_PREFIX + feature);
+ // Override precedence:
+ // Settings.Global -> sys.fflag.override.* -> sys.fflag.*
+
+ // Step 1: check if feature flag is set in Settings.Global.
+ String value;
+ if (context != null) {
+ value = Settings.Global.getString(context.getContentResolver(), feature);
+ if (!TextUtils.isEmpty(value)) {
+ return Boolean.parseBoolean(value);
+ }
+ }
+
+ // Step 2: check if feature flag has any override. Flag name: sys.fflag.override.<feature>
+ value = SystemProperties.get(FFLAG_OVERRIDE_PREFIX + feature);
if (!TextUtils.isEmpty(value)) {
return Boolean.parseBoolean(value);
}
- // Step 2: check if feature flag has any default value. Flag name: sys.fflag.<feature>
+ // Step 3: check if feature flag has any default value. Flag name: sys.fflag.<feature>
value = SystemProperties.get(FFLAG_PREFIX + feature);
return Boolean.parseBoolean(value);
}
@@ -53,7 +65,7 @@ public class FeatureFlagUtils {
/**
* Override feature flag to new state.
*/
- public static void setEnabled(String feature, boolean enabled) {
+ public static void setEnabled(Context context, String feature, boolean enabled) {
SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false");
}
diff --git a/android/util/Log.java b/android/util/Log.java
index b94e48b3..02998653 100644
--- a/android/util/Log.java
+++ b/android/util/Log.java
@@ -16,12 +16,45 @@
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;
/**
- * Mock Log implementation for testing on non android host.
+ * 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.
*/
public final class Log {
@@ -55,6 +88,29 @@ 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() {
}
@@ -65,7 +121,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int v(String tag, String msg) {
- return println(LOG_ID_MAIN, VERBOSE, tag, msg);
+ return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}
/**
@@ -76,7 +132,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int v(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
}
/**
@@ -86,7 +142,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int d(String tag, String msg) {
- return println(LOG_ID_MAIN, DEBUG, tag, msg);
+ return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
/**
@@ -97,7 +153,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int d(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
}
/**
@@ -107,7 +163,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int i(String tag, String msg) {
- return println(LOG_ID_MAIN, INFO, tag, msg);
+ return println_native(LOG_ID_MAIN, INFO, tag, msg);
}
/**
@@ -118,7 +174,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int i(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
}
/**
@@ -128,7 +184,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int w(String tag, String msg) {
- return println(LOG_ID_MAIN, WARN, tag, msg);
+ return println_native(LOG_ID_MAIN, WARN, tag, msg);
}
/**
@@ -139,9 +195,31 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, WARN, tag, msg, 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
@@ -149,7 +227,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, Throwable tr) {
- return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
}
/**
@@ -159,7 +237,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int e(String tag, String msg) {
- return println(LOG_ID_MAIN, ERROR, tag, msg);
+ return println_native(LOG_ID_MAIN, ERROR, tag, msg);
}
/**
@@ -170,7 +248,82 @@ public final class Log {
* @param tr An exception to log
*/
public static int e(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(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;
}
/**
@@ -193,7 +346,7 @@ public final class Log {
}
StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
+ PrintWriter pw = new FastPrintWriter(sw, false, 256);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
@@ -208,7 +361,7 @@ public final class Log {
* @return The number of bytes written.
*/
public static int println(int priority, String tag, String msg) {
- return println(LOG_ID_MAIN, priority, tag, msg);
+ return println_native(LOG_ID_MAIN, priority, tag, msg);
}
/** @hide */ public static final int LOG_ID_MAIN = 0;
@@ -217,9 +370,115 @@ public final class Log {
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
/** @hide */ public static final int LOG_ID_CRASH = 4;
- /** @hide */ @SuppressWarnings("unused")
- public static int println(int bufID,
- int priority, String tag, String msg) {
- return 0;
+ /** @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.
+ }
}
}
diff --git a/android/util/StatsManager.java b/android/util/StatsManager.java
index 55b33a61..2bcd863c 100644
--- a/android/util/StatsManager.java
+++ b/android/util/StatsManager.java
@@ -58,11 +58,12 @@ public final class StatsManager {
try {
IStatsManager service = getIStatsManagerLocked();
if (service == null) {
- throw new RuntimeException("StatsD service connection lost");
+ Slog.d(TAG, "Failed to find statsd when adding configuration");
+ return false;
}
return service.addConfiguration(configKey, config, pkg, cls);
} catch (RemoteException e) {
- Slog.d(TAG, "Failed to connect to statsd when getting data");
+ Slog.d(TAG, "Failed to connect to statsd when adding configuration");
return false;
}
}
@@ -80,11 +81,12 @@ public final class StatsManager {
try {
IStatsManager service = getIStatsManagerLocked();
if (service == null) {
- throw new RuntimeException("StatsD service connection lost");
+ Slog.d(TAG, "Failed to find statsd when removing configuration");
+ return false;
}
return service.removeConfiguration(configKey);
} catch (RemoteException e) {
- Slog.d(TAG, "Failed to connect to statsd when getting data");
+ Slog.d(TAG, "Failed to connect to statsd when removing configuration");
return false;
}
}
@@ -102,7 +104,8 @@ public final class StatsManager {
try {
IStatsManager service = getIStatsManagerLocked();
if (service == null) {
- throw new RuntimeException("StatsD service connection lost");
+ Slog.d(TAG, "Failed to find statsd when getting data");
+ return null;
}
return service.getData(configKey);
} catch (RemoteException e) {
diff --git a/android/util/proto/ProtoUtils.java b/android/util/proto/ProtoUtils.java
index 449baca5..85b7ec82 100644
--- a/android/util/proto/ProtoUtils.java
+++ b/android/util/proto/ProtoUtils.java
@@ -17,6 +17,7 @@
package android.util.proto;
import android.util.AggStats;
+import android.util.Duration;
/**
* This class contains a list of helper functions to write common proto in
@@ -36,4 +37,15 @@ public class ProtoUtils {
proto.write(AggStats.MAX, max);
proto.end(aggStatsToken);
}
+
+ /**
+ * Dump Duration to ProtoOutputStream
+ * @hide
+ */
+ public static void toDuration(ProtoOutputStream proto, long fieldId, long startMs, long endMs) {
+ final long token = proto.start(fieldId);
+ proto.write(Duration.START_MS, startMs);
+ proto.write(Duration.END_MS, endMs);
+ proto.end(token);
+ }
}
diff --git a/android/view/Display.java b/android/view/Display.java
index 6a44cdb9..9673be01 100644
--- a/android/view/Display.java
+++ b/android/view/Display.java
@@ -20,6 +20,7 @@ import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
import android.annotation.IntDef;
import android.annotation.RequiresPermission;
+import android.app.KeyguardManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -209,8 +210,8 @@ public final class Display {
* </p>
*
* @see DisplayManager#VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD
- * @see WindowManagerPolicy#isKeyguardSecure(int)
- * @see WindowManagerPolicy#isKeyguardTrustedLw()
+ * @see KeyguardManager#isDeviceSecure()
+ * @see KeyguardManager#isDeviceLocked()
* @see #getFlags
* @hide
*/
diff --git a/android/view/DisplayCutout.java b/android/view/DisplayCutout.java
new file mode 100644
index 00000000..19cd42e1
--- /dev/null
+++ b/android/view/DisplayCutout.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a part of the display that is not functional for displaying content.
+ *
+ * <p>{@code DisplayCutout} is immutable.
+ *
+ * @hide will become API
+ */
+public final class DisplayCutout {
+
+ private static final Rect ZERO_RECT = new Rect(0, 0, 0, 0);
+ private static final ArrayList<Point> EMPTY_LIST = new ArrayList<>();
+
+ /**
+ * An instance where {@link #hasCutout()} returns {@code false}.
+ *
+ * @hide
+ */
+ public static final DisplayCutout NO_CUTOUT =
+ new DisplayCutout(ZERO_RECT, ZERO_RECT, EMPTY_LIST);
+
+ private final Rect mSafeInsets;
+ private final Rect mBoundingRect;
+ private final List<Point> mBoundingPolygon;
+
+ /**
+ * Creates a DisplayCutout instance.
+ *
+ * NOTE: the Rects passed into this instance are not copied and MUST remain unchanged.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public DisplayCutout(Rect safeInsets, Rect boundingRect, List<Point> boundingPolygon) {
+ mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT;
+ mBoundingRect = boundingRect != null ? boundingRect : ZERO_RECT;
+ mBoundingPolygon = boundingPolygon != null ? boundingPolygon : EMPTY_LIST;
+ }
+
+ /**
+ * Returns whether there is a cutout.
+ *
+ * If false, the safe insets will all return zero, and the bounding box or polygon will be
+ * empty or outside the content view.
+ *
+ * @return {@code true} if there is a cutout, {@code false} otherwise
+ */
+ public boolean hasCutout() {
+ return !mSafeInsets.equals(ZERO_RECT);
+ }
+
+ /** Returns the inset from the top which avoids the display cutout. */
+ public int getSafeInsetTop() {
+ return mSafeInsets.top;
+ }
+
+ /** Returns the inset from the bottom which avoids the display cutout. */
+ public int getSafeInsetBottom() {
+ return mSafeInsets.bottom;
+ }
+
+ /** Returns the inset from the left which avoids the display cutout. */
+ public int getSafeInsetLeft() {
+ return mSafeInsets.left;
+ }
+
+ /** Returns the inset from the right which avoids the display cutout. */
+ public int getSafeInsetRight() {
+ return mSafeInsets.right;
+ }
+
+ /**
+ * Obtains the safe insets in a rect.
+ *
+ * @param out a rect which is set to the safe insets.
+ * @hide
+ */
+ public void getSafeInsets(@NonNull Rect out) {
+ out.set(mSafeInsets);
+ }
+
+ /**
+ * Obtains the bounding rect of the cutout.
+ *
+ * @param outRect is filled with the bounding rect of the cutout. Coordinates are relative
+ * to the top-left corner of the content view.
+ */
+ public void getBoundingRect(@NonNull Rect outRect) {
+ outRect.set(mBoundingRect);
+ }
+
+ /**
+ * Obtains the bounding polygon of the cutout.
+ *
+ * @param outPolygon is filled with a list of points representing the corners of a convex
+ * polygon which covers the cutout. Coordinates are relative to the
+ * top-left corner of the content view.
+ */
+ public void getBoundingPolygon(List<Point> outPolygon) {
+ outPolygon.clear();
+ for (int i = 0; i < mBoundingPolygon.size(); i++) {
+ outPolygon.add(new Point(mBoundingPolygon.get(i)));
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mSafeInsets.hashCode();
+ result = result * 31 + mBoundingRect.hashCode();
+ result = result * 31 + mBoundingPolygon.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof DisplayCutout) {
+ DisplayCutout c = (DisplayCutout) o;
+ return mSafeInsets.equals(c.mSafeInsets)
+ && mBoundingRect.equals(c.mBoundingRect)
+ && mBoundingPolygon.equals(c.mBoundingPolygon);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "DisplayCutout{insets=" + mSafeInsets
+ + " bounding=" + mBoundingRect
+ + "}";
+ }
+
+ /**
+ * Insets the reference frame of the cutout in the given directions.
+ *
+ * @return a copy of this instance which has been inset
+ * @hide
+ */
+ public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
+ if (mBoundingRect.isEmpty()
+ || insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) {
+ return this;
+ }
+
+ Rect safeInsets = new Rect(mSafeInsets);
+ Rect boundingRect = new Rect(mBoundingRect);
+ ArrayList<Point> boundingPolygon = new ArrayList<>();
+ getBoundingPolygon(boundingPolygon);
+
+ // Note: it's not really well defined what happens when the inset is negative, because we
+ // don't know if the safe inset needs to expand in general.
+ if (insetTop > 0 || safeInsets.top > 0) {
+ safeInsets.top = atLeastZero(safeInsets.top - insetTop);
+ }
+ if (insetBottom > 0 || safeInsets.bottom > 0) {
+ safeInsets.bottom = atLeastZero(safeInsets.bottom - insetBottom);
+ }
+ if (insetLeft > 0 || safeInsets.left > 0) {
+ safeInsets.left = atLeastZero(safeInsets.left - insetLeft);
+ }
+ if (insetRight > 0 || safeInsets.right > 0) {
+ safeInsets.right = atLeastZero(safeInsets.right - insetRight);
+ }
+
+ boundingRect.offset(-insetLeft, -insetTop);
+ offset(boundingPolygon, -insetLeft, -insetTop);
+
+ return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
+ }
+
+ /**
+ * Calculates the safe insets relative to the given reference frame.
+ *
+ * @return a copy of this instance with the safe insets calculated
+ * @hide
+ */
+ public DisplayCutout calculateRelativeTo(Rect frame) {
+ if (mBoundingRect.isEmpty() || !Rect.intersects(frame, mBoundingRect)) {
+ return NO_CUTOUT;
+ }
+
+ Rect boundingRect = new Rect(mBoundingRect);
+ ArrayList<Point> boundingPolygon = new ArrayList<>();
+ getBoundingPolygon(boundingPolygon);
+
+ return DisplayCutout.calculateRelativeTo(frame, boundingRect, boundingPolygon);
+ }
+
+ private static DisplayCutout calculateRelativeTo(Rect frame, Rect boundingRect,
+ ArrayList<Point> boundingPolygon) {
+ Rect safeRect = new Rect();
+ int bestArea = 0;
+ int bestVariant = 0;
+ for (int variant = ROTATION_0; variant <= ROTATION_270; variant++) {
+ int area = calculateInsetVariantArea(frame, boundingRect, variant, safeRect);
+ if (bestArea < area) {
+ bestArea = area;
+ bestVariant = variant;
+ }
+ }
+ calculateInsetVariantArea(frame, boundingRect, bestVariant, safeRect);
+ if (safeRect.isEmpty()) {
+ // The entire frame overlaps with the cutout.
+ safeRect.set(0, frame.height(), 0, 0);
+ } else {
+ // Convert safeRect to insets relative to frame. We're reusing the rect here to avoid
+ // an allocation.
+ safeRect.set(
+ Math.max(0, safeRect.left - frame.left),
+ Math.max(0, safeRect.top - frame.top),
+ Math.max(0, frame.right - safeRect.right),
+ Math.max(0, frame.bottom - safeRect.bottom));
+ }
+
+ boundingRect.offset(-frame.left, -frame.top);
+ offset(boundingPolygon, -frame.left, -frame.top);
+
+ return new DisplayCutout(safeRect, boundingRect, boundingPolygon);
+ }
+
+ private static int calculateInsetVariantArea(Rect frame, Rect boundingRect, int variant,
+ Rect outSafeRect) {
+ switch (variant) {
+ case ROTATION_0:
+ outSafeRect.set(frame.left, frame.top, frame.right, boundingRect.top);
+ break;
+ case ROTATION_90:
+ outSafeRect.set(frame.left, frame.top, boundingRect.left, frame.bottom);
+ break;
+ case ROTATION_180:
+ outSafeRect.set(frame.left, boundingRect.bottom, frame.right, frame.bottom);
+ break;
+ case ROTATION_270:
+ outSafeRect.set(boundingRect.right, frame.top, frame.right, frame.bottom);
+ break;
+ }
+
+ return outSafeRect.isEmpty() ? 0 : outSafeRect.width() * outSafeRect.height();
+ }
+
+ private static int atLeastZero(int value) {
+ return value < 0 ? 0 : value;
+ }
+
+ private static void offset(ArrayList<Point> points, int dx, int dy) {
+ for (int i = 0; i < points.size(); i++) {
+ points.get(i).offset(dx, dy);
+ }
+ }
+
+ /**
+ * Creates an instance from a bounding polygon.
+ *
+ * @hide
+ */
+ public static DisplayCutout fromBoundingPolygon(List<Point> points) {
+ Rect boundingRect = new Rect(Integer.MAX_VALUE, Integer.MAX_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE);
+ ArrayList<Point> boundingPolygon = new ArrayList<>();
+
+ for (int i = 0; i < points.size(); i++) {
+ Point point = points.get(i);
+ boundingRect.left = Math.min(boundingRect.left, point.x);
+ boundingRect.right = Math.max(boundingRect.right, point.x);
+ boundingRect.top = Math.min(boundingRect.top, point.y);
+ boundingRect.bottom = Math.max(boundingRect.bottom, point.y);
+ boundingPolygon.add(new Point(point));
+ }
+
+ return new DisplayCutout(ZERO_RECT, boundingRect, boundingPolygon);
+ }
+
+ /**
+ * Helper class for passing {@link DisplayCutout} through binder.
+ *
+ * Needed, because {@code readFromParcel} cannot be used with immutable classes.
+ *
+ * @hide
+ */
+ public static final class ParcelableWrapper implements Parcelable {
+
+ private DisplayCutout mInner;
+
+ public ParcelableWrapper() {
+ this(NO_CUTOUT);
+ }
+
+ public ParcelableWrapper(DisplayCutout cutout) {
+ mInner = cutout;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (mInner == NO_CUTOUT) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(1);
+ out.writeTypedObject(mInner.mSafeInsets, flags);
+ out.writeTypedObject(mInner.mBoundingRect, flags);
+ out.writeTypedList(mInner.mBoundingPolygon, flags);
+ }
+ }
+
+ /**
+ * Similar to {@link Creator#createFromParcel(Parcel)}, but reads into an existing
+ * instance.
+ *
+ * Needed for AIDL out parameters.
+ */
+ public void readFromParcel(Parcel in) {
+ mInner = readCutout(in);
+ }
+
+ public static final Creator<ParcelableWrapper> CREATOR = new Creator<ParcelableWrapper>() {
+ @Override
+ public ParcelableWrapper createFromParcel(Parcel in) {
+ return new ParcelableWrapper(readCutout(in));
+ }
+
+ @Override
+ public ParcelableWrapper[] newArray(int size) {
+ return new ParcelableWrapper[size];
+ }
+ };
+
+ private static DisplayCutout readCutout(Parcel in) {
+ if (in.readInt() == 0) {
+ return NO_CUTOUT;
+ }
+
+ ArrayList<Point> boundingPolygon = new ArrayList<>();
+
+ Rect safeInsets = in.readTypedObject(Rect.CREATOR);
+ Rect boundingRect = in.readTypedObject(Rect.CREATOR);
+ in.readTypedList(boundingPolygon, Point.CREATOR);
+
+ return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
+ }
+
+ public DisplayCutout get() {
+ return mInner;
+ }
+
+ public void set(ParcelableWrapper cutout) {
+ mInner = cutout.get();
+ }
+
+ public void set(DisplayCutout cutout) {
+ mInner = cutout;
+ }
+
+ @Override
+ public int hashCode() {
+ return mInner.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof ParcelableWrapper
+ && mInner.equals(((ParcelableWrapper) o).mInner);
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(mInner);
+ }
+ }
+}
diff --git a/android/view/Gravity.java b/android/view/Gravity.java
index 232ff255..defa58e1 100644
--- a/android/view/Gravity.java
+++ b/android/view/Gravity.java
@@ -446,50 +446,53 @@ public class Gravity
*/
public static String toString(int gravity) {
final StringBuilder result = new StringBuilder();
- if ((gravity & FILL) != 0) {
+ if ((gravity & FILL) == FILL) {
result.append("FILL").append(' ');
} else {
- if ((gravity & FILL_VERTICAL) != 0) {
+ if ((gravity & FILL_VERTICAL) == FILL_VERTICAL) {
result.append("FILL_VERTICAL").append(' ');
} else {
- if ((gravity & TOP) != 0) {
+ if ((gravity & TOP) == TOP) {
result.append("TOP").append(' ');
}
- if ((gravity & BOTTOM) != 0) {
+ if ((gravity & BOTTOM) == BOTTOM) {
result.append("BOTTOM").append(' ');
}
}
- if ((gravity & FILL_HORIZONTAL) != 0) {
+ if ((gravity & FILL_HORIZONTAL) == FILL_HORIZONTAL) {
result.append("FILL_HORIZONTAL").append(' ');
} else {
- if ((gravity & START) != 0) {
+ if ((gravity & START) == START) {
result.append("START").append(' ');
- } else if ((gravity & LEFT) != 0) {
+ } else if ((gravity & LEFT) == LEFT) {
result.append("LEFT").append(' ');
}
- if ((gravity & END) != 0) {
+ if ((gravity & END) == END) {
result.append("END").append(' ');
- } else if ((gravity & RIGHT) != 0) {
+ } else if ((gravity & RIGHT) == RIGHT) {
result.append("RIGHT").append(' ');
}
}
}
- if ((gravity & CENTER) != 0) {
+ if ((gravity & CENTER) == CENTER) {
result.append("CENTER").append(' ');
} else {
- if ((gravity & CENTER_VERTICAL) != 0) {
+ if ((gravity & CENTER_VERTICAL) == CENTER_VERTICAL) {
result.append("CENTER_VERTICAL").append(' ');
}
- if ((gravity & CENTER_HORIZONTAL) != 0) {
+ if ((gravity & CENTER_HORIZONTAL) == CENTER_HORIZONTAL) {
result.append("CENTER_HORIZONTAL").append(' ');
}
}
- if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) {
- result.append("DISPLAY_CLIP_VERTICAL").append(' ');
+ if (result.length() == 0) {
+ result.append("NO GRAVITY").append(' ');
}
- if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) {
+ if ((gravity & DISPLAY_CLIP_VERTICAL) == DISPLAY_CLIP_VERTICAL) {
result.append("DISPLAY_CLIP_VERTICAL").append(' ');
}
+ if ((gravity & DISPLAY_CLIP_HORIZONTAL) == DISPLAY_CLIP_HORIZONTAL) {
+ result.append("DISPLAY_CLIP_HORIZONTAL").append(' ');
+ }
result.deleteCharAt(result.length() - 1);
return result.toString();
}
diff --git a/android/view/InputFilter.java b/android/view/InputFilter.java
index d0dab400..0ab4dc02 100644
--- a/android/view/InputFilter.java
+++ b/android/view/InputFilter.java
@@ -72,21 +72,21 @@ import android.os.RemoteException;
* </p><p>
* The early policy interception decides whether an input event should be delivered
* to applications or dropped. The policy indicates its decision by setting the
- * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} policy flag. The input filter may
+ * {@link WindowManagerPolicyConstants#FLAG_PASS_TO_USER} policy flag. The input filter may
* sometimes receive events that do not have this flag set. It should take note of
* the fact that the policy intends to drop the event, clean up its state, and
* then send appropriate cancellation events to the dispatcher if needed.
* </p><p>
* For example, suppose the input filter is processing a gesture and one of the touch events
- * it receives does not have the {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag set.
+ * it receives does not have the {@link WindowManagerPolicyConstants#FLAG_PASS_TO_USER} flag set.
* The input filter should clear its internal state about the gesture and then send key or
* motion events to the dispatcher to cancel any keys or pointers that are down.
* </p><p>
* Corollary: Events that get sent to the dispatcher should usually include the
- * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped!
+ * {@link WindowManagerPolicyConstants#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped!
* </p><p>
* It may be prudent to disable automatic key repeating for synthetic key events
- * by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag.
+ * by setting the {@link WindowManagerPolicyConstants#FLAG_DISABLE_KEY_REPEAT} policy flag.
* </p>
*
* @hide
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index 5641009c..3d01ec23 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -56,11 +56,15 @@ public class SurfaceControl {
Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
boolean allLayers, boolean useIdentityTransform);
private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer,
- int rotation);
+ Rect sourceCrop, float frameScale);
+ private static native GraphicBuffer nativeCaptureLayers(IBinder layerHandleToken,
+ Rect sourceCrop, float frameScale);
private static native long nativeCreateTransaction();
private static native long nativeGetNativeTransactionFinalizer();
private static native void nativeApplyTransaction(long transactionObj, boolean sync);
+ private static native void nativeMergeTransaction(long transactionObj,
+ long otherTransactionObj);
private static native void nativeSetAnimationTransaction(long transactionObj);
private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder);
@@ -654,6 +658,19 @@ public class SurfaceControl {
}
}
+ /**
+ * Merge the supplied transaction in to the deprecated "global" transaction.
+ * This clears the supplied transaction in an identical fashion to {@link Transaction#merge}.
+ * <p>
+ * This is a utility for interop with legacy-code and will go away with the Global Transaction.
+ */
+ @Deprecated
+ public static void mergeToGlobalTransaction(Transaction t) {
+ synchronized(sGlobalTransaction) {
+ sGlobalTransaction.merge(t);
+ }
+ }
+
/** end a transaction */
public static void closeTransaction() {
closeTransaction(false);
@@ -1162,14 +1179,24 @@ public class SurfaceControl {
* Captures a layer and its children into the provided {@link Surface}.
*
* @param layerHandleToken The root layer to capture.
- * @param consumer The {@link Surface} to capture the layer into.
- * @param rotation Apply a custom clockwise rotation to the screenshot, i.e.
- * Surface.ROTATION_0,90,180,270. Surfaceflinger will always capture in its
- * native portrait orientation by default, so this is useful for returning
- * captures that are independent of device orientation.
+ * @param consumer The {@link Surface} to capture the layer into.
+ * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
+ * Rect()' or null if no cropping is desired.
+ * @param frameScale The desired scale of the returned buffer; the raw
+ * screen will be scaled up/down.
+ */
+ public static void captureLayers(IBinder layerHandleToken, Surface consumer, Rect sourceCrop,
+ float frameScale) {
+ nativeCaptureLayers(layerHandleToken, consumer, sourceCrop, frameScale);
+ }
+
+ /**
+ * Same as {@link #captureLayers(IBinder, Surface, Rect, float)} except this
+ * captures to a {@link GraphicBuffer} instead of a {@link Surface}.
*/
- public static void captureLayers(IBinder layerHandleToken, Surface consumer, int rotation) {
- nativeCaptureLayers(layerHandleToken, consumer, rotation);
+ public static GraphicBuffer captureLayersToBuffer(IBinder layerHandleToken, Rect sourceCrop,
+ float frameScale) {
+ return nativeCaptureLayers(layerHandleToken, sourceCrop, frameScale);
}
public static class Transaction implements Closeable {
@@ -1368,7 +1395,7 @@ public class SurfaceControl {
* 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) {
+ public Transaction setSecure(SurfaceControl sc, boolean isSecure) {
sc.checkNotReleased();
if (isSecure) {
nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
@@ -1449,5 +1476,14 @@ public class SurfaceControl {
nativeSetAnimationTransaction(mNativeObject);
return this;
}
+
+ /**
+ * Merge the other transaction into this transaction, clearing the
+ * other transaction as if it had been applied.
+ */
+ public Transaction merge(Transaction other) {
+ nativeMergeTransaction(mNativeObject, other.mNativeObject);
+ return this;
+ }
}
}
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index 4eab496e..578679b1 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -17,9 +17,9 @@
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 static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER;
import android.content.Context;
import android.content.res.CompatibilityInfo.Translator;
diff --git a/android/view/View.java b/android/view/View.java
index be09fe86..0525ab16 100644
--- a/android/view/View.java
+++ b/android/view/View.java
@@ -2929,6 +2929,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* 1 PFLAG3_TEMPORARY_DETACH
* 1 PFLAG3_NO_REVEAL_ON_FOCUS
* 1 PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT
+ * 1 PFLAG3_SCREEN_READER_FOCUSABLE
* |-------|-------|-------|-------|
*/
@@ -3209,6 +3210,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
static final int PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT = 0x8000000;
+ /**
+ * Works like focusable for screen readers, but without the side effects on input focus.
+ * @see #setScreenReaderFocusable(boolean)
+ */
+ private static final int PFLAG3_SCREEN_READER_FOCUSABLE = 0x10000000;
+
/* End of masks for mPrivateFlags3 */
/**
@@ -4245,6 +4252,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
OnCapturedPointerListener mOnCapturedPointerListener;
+
+ private ArrayList<OnKeyFallbackListener> mKeyFallbackListeners;
}
ListenerInfo mListenerInfo;
@@ -5342,6 +5351,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
setDefaultFocusHighlightEnabled(a.getBoolean(attr, true));
}
break;
+ case R.styleable.View_screenReaderFocusable:
+ if (a.peekValue(attr) != null) {
+ setScreenReaderFocusable(a.getBoolean(attr, false));
+ }
+ break;
}
}
@@ -7194,7 +7208,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param text The announcement text.
*/
public void announceForAccessibility(CharSequence text) {
- if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_ANNOUNCEMENT) && mParent != null) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_ANNOUNCEMENT);
onInitializeAccessibilityEvent(event);
@@ -8392,6 +8407,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
info.setEnabled(isEnabled());
info.setClickable(isClickable());
info.setFocusable(isFocusable());
+ info.setScreenReaderFocusable(isScreenReaderFocusable());
info.setFocused(isFocused());
info.setAccessibilityFocused(isAccessibilityFocused());
info.setSelected(isSelected());
@@ -10348,6 +10364,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Returns whether the view should be treated as a focusable unit by screen reader
+ * accessibility tools.
+ * @see #setScreenReaderFocusable(boolean)
+ *
+ * @return Whether the view should be treated as a focusable unit by screen reader.
+ */
+ public boolean isScreenReaderFocusable() {
+ return (mPrivateFlags3 & PFLAG3_SCREEN_READER_FOCUSABLE) != 0;
+ }
+
+ /**
+ * When screen readers (one type of accessibility tool) decide what should be read to the
+ * user, they typically look for input focusable ({@link #isFocusable()}) parents of
+ * non-focusable text items, and read those focusable parents and their non-focusable children
+ * as a unit. In some situations, this behavior is desirable for views that should not take
+ * input focus. Setting an item to be screen reader focusable requests that the view be
+ * treated as a unit by screen readers without any effect on input focusability. The default
+ * value of {@code false} lets screen readers use other signals, like focusable, to determine
+ * how to group items.
+ *
+ * @param screenReaderFocusable Whether the view should be treated as a unit by screen reader
+ * accessibility tools.
+ */
+ public void setScreenReaderFocusable(boolean screenReaderFocusable) {
+ int pflags3 = mPrivateFlags3;
+ if (screenReaderFocusable) {
+ pflags3 |= PFLAG3_SCREEN_READER_FOCUSABLE;
+ } else {
+ pflags3 &= ~PFLAG3_SCREEN_READER_FOCUSABLE;
+ }
+
+ if (pflags3 != mPrivateFlags3) {
+ mPrivateFlags3 = pflags3;
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ }
+
+ /**
* Find the nearest view in the specified direction that can take focus.
* This does not actually give focus to that view.
*
@@ -10913,7 +10968,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) {
mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED;
invalidate();
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED)) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
event.setAction(action);
@@ -11738,7 +11794,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private void sendViewTextTraversedAtGranularityEvent(int action, int granularity,
int fromIndex, int toIndex) {
- if (mParent == null) {
+ if (mParent == null || !AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY)) {
return;
}
AccessibilityEvent event = AccessibilityEvent.obtain(
@@ -25194,6 +25251,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Interface definition for a callback to be invoked when a hardware key event is
+ * dispatched to this view during the fallback phase. This means no view in the hierarchy
+ * has handled this event.
+ */
+ public interface OnKeyFallbackListener {
+ /**
+ * Called when a hardware key is dispatched to a view in the fallback phase. This allows
+ * listeners to respond to events after the view hierarchy has had a chance to respond.
+ * <p>Key presses in software keyboards will generally NOT trigger this method,
+ * although some may elect to do so in some situations. Do not assume a
+ * software input method has to be key-based; even if it is, it may use key presses
+ * in a different way than you expect, so there is no way to reliably catch soft
+ * input key presses.
+ *
+ * @param v The view the key has been dispatched to.
+ * @param event The KeyEvent object containing full information about
+ * the event.
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ boolean onKeyFallback(View v, KeyEvent event);
+ }
+
+ /**
* Interface definition for a callback to be invoked when a touch event is
* dispatched to this view. The callback will be invoked before the touch
* event is given to the view.
@@ -26105,7 +26185,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@Override
public void run() {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_SCROLLED)) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_SCROLLED);
event.setScrollDeltaX(mDeltaX);
@@ -26866,4 +26947,56 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
return mTooltipInfo.mTooltipPopup.getContentView();
}
+
+ /**
+ * Allows this view to handle {@link KeyEvent}s which weren't handled by normal dispatch. This
+ * occurs after the normal view hierarchy dispatch, but before the window callback. By default,
+ * this will dispatch into all the listeners registered via
+ * {@link #addKeyFallbackListener(OnKeyFallbackListener)} in last-in-first-out order (most
+ * recently added will receive events first).
+ *
+ * @param event A not-previously-handled event.
+ * @return {@code true} if the event was handled, {@code false} otherwise.
+ * @see #addKeyFallbackListener
+ */
+ public boolean onKeyFallback(@NonNull KeyEvent event) {
+ if (mListenerInfo != null && mListenerInfo.mKeyFallbackListeners != null) {
+ for (int i = mListenerInfo.mKeyFallbackListeners.size() - 1; i >= 0; --i) {
+ if (mListenerInfo.mKeyFallbackListeners.get(i).onKeyFallback(this, event)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Adds a listener which will receive unhandled {@link KeyEvent}s.
+ * @param listener the receiver of fallback {@link KeyEvent}s.
+ * @see #onKeyFallback(KeyEvent)
+ */
+ public void addKeyFallbackListener(OnKeyFallbackListener listener) {
+ ArrayList<OnKeyFallbackListener> fallbacks = getListenerInfo().mKeyFallbackListeners;
+ if (fallbacks == null) {
+ fallbacks = new ArrayList<>();
+ getListenerInfo().mKeyFallbackListeners = fallbacks;
+ }
+ fallbacks.add(listener);
+ }
+
+ /**
+ * Removes a listener which will receive unhandled {@link KeyEvent}s.
+ * @param listener the receiver of fallback {@link KeyEvent}s.
+ * @see #onKeyFallback(KeyEvent)
+ */
+ public void removeKeyFallbackListener(OnKeyFallbackListener listener) {
+ if (mListenerInfo != null) {
+ if (mListenerInfo.mKeyFallbackListeners != null) {
+ mListenerInfo.mKeyFallbackListeners.remove(listener);
+ if (mListenerInfo.mKeyFallbackListeners.isEmpty()) {
+ mListenerInfo.mKeyFallbackListeners = null;
+ }
+ }
+ }
+ }
}
diff --git a/android/view/ViewGroup.java b/android/view/ViewGroup.java
index 929beaea..122df934 100644
--- a/android/view/ViewGroup.java
+++ b/android/view/ViewGroup.java
@@ -2591,7 +2591,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
- // If the event is targeting accessiiblity focus we give it to the
+ // If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index e30496fb..1c9d8639 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -73,6 +73,7 @@ import android.util.Log;
import android.util.MergedConfiguration;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import android.util.TypedValue;
import android.view.Surface.OutOfResourcesException;
@@ -362,6 +363,8 @@ public final class ViewRootImpl implements ViewParent,
InputStage mFirstPostImeInputStage;
InputStage mSyntheticInputStage;
+ private final KeyFallbackManager mKeyFallbackManager = new KeyFallbackManager();
+
boolean mWindowAttributesChanged = false;
int mWindowAttributesChangesFlag = 0;
@@ -1560,7 +1563,7 @@ public final class ViewRootImpl implements ViewParent,
mLastWindowInsets = new WindowInsets(contentInsets,
null /* windowDecorInsets */, stableInsets,
mContext.getResources().getConfiguration().isScreenRound(),
- mAttachInfo.mAlwaysConsumeNavBar);
+ mAttachInfo.mAlwaysConsumeNavBar, null /* displayCutout */);
}
return mLastWindowInsets;
}
@@ -4764,6 +4767,13 @@ public final class ViewRootImpl implements ViewParent,
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
+ mKeyFallbackManager.mDispatched = false;
+
+ if (mKeyFallbackManager.hasFocus()
+ && mKeyFallbackManager.dispatchUnique(mView, event)) {
+ return FINISH_HANDLED;
+ }
+
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
@@ -4773,6 +4783,10 @@ public final class ViewRootImpl implements ViewParent,
return FINISH_NOT_HANDLED;
}
+ if (mKeyFallbackManager.dispatchUnique(mView, event)) {
+ return FINISH_HANDLED;
+ }
+
int groupNavigationDirection = 0;
if (event.getAction() == KeyEvent.ACTION_DOWN
@@ -7529,6 +7543,16 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ /**
+ * Dispatches a KeyEvent to all registered key fallback handlers.
+ *
+ * @param event
+ * @return {@code true} if the event was handled, {@code false} otherwise.
+ */
+ public boolean dispatchKeyFallbackEvent(KeyEvent event) {
+ return mKeyFallbackManager.dispatch(mView, event);
+ }
+
class TakenSurfaceHolder extends BaseSurfaceHolder {
@Override
public boolean onAllowLockCanvas() {
@@ -8093,4 +8117,92 @@ public final class ViewRootImpl implements ViewParent,
run();
}
}
+
+ private static class KeyFallbackManager {
+
+ // This is used to ensure that key-fallback events are only dispatched once. We attempt
+ // to dispatch more than once in order to achieve a certain order. Specifically, if we
+ // are in an Activity or Dialog (and have a Window.Callback), the keyfallback events should
+ // be dispatched after the view hierarchy, but before the Activity. However, if we aren't
+ // in an activity, we still want key fallbacks to be dispatched.
+ boolean mDispatched = false;
+
+ SparseBooleanArray mCapturedKeys = new SparseBooleanArray();
+ WeakReference<View> mFallbackReceiver = null;
+ int mVisitCount = 0;
+
+ private void updateCaptureState(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ mCapturedKeys.append(event.getKeyCode(), true);
+ }
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ mCapturedKeys.delete(event.getKeyCode());
+ }
+ }
+
+ boolean dispatch(View root, KeyEvent event) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "KeyFallback dispatch");
+ mDispatched = true;
+
+ updateCaptureState(event);
+
+ if (mFallbackReceiver != null) {
+ View target = mFallbackReceiver.get();
+ if (mCapturedKeys.size() == 0) {
+ mFallbackReceiver = null;
+ }
+ if (target != null && target.isAttachedToWindow()) {
+ return target.onKeyFallback(event);
+ }
+ // consume anyways so that we don't feed uncaptured key events to other views
+ return true;
+ }
+
+ boolean result = dispatchInZOrder(root, event);
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ return result;
+ }
+
+ private boolean dispatchInZOrder(View view, KeyEvent evt) {
+ if (view instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) view;
+ ArrayList<View> orderedViews = vg.buildOrderedChildList();
+ if (orderedViews != null) {
+ try {
+ for (int i = orderedViews.size() - 1; i >= 0; --i) {
+ View v = orderedViews.get(i);
+ if (dispatchInZOrder(v, evt)) {
+ return true;
+ }
+ }
+ } finally {
+ orderedViews.clear();
+ }
+ } else {
+ for (int i = vg.getChildCount() - 1; i >= 0; --i) {
+ View v = vg.getChildAt(i);
+ if (dispatchInZOrder(v, evt)) {
+ return true;
+ }
+ }
+ }
+ }
+ if (view.onKeyFallback(evt)) {
+ mFallbackReceiver = new WeakReference<>(view);
+ return true;
+ }
+ return false;
+ }
+
+ boolean hasFocus() {
+ return mFallbackReceiver != null;
+ }
+
+ boolean dispatchUnique(View root, KeyEvent event) {
+ if (mDispatched) {
+ return false;
+ }
+ return dispatch(root, event);
+ }
+ }
}
diff --git a/android/view/WindowInsets.java b/android/view/WindowInsets.java
index 750931ab..df124ac5 100644
--- a/android/view/WindowInsets.java
+++ b/android/view/WindowInsets.java
@@ -17,6 +17,7 @@
package android.view;
+import android.annotation.NonNull;
import android.graphics.Rect;
/**
@@ -36,6 +37,7 @@ public final class WindowInsets {
private Rect mStableInsets;
private Rect mTempRect;
private boolean mIsRound;
+ private DisplayCutout mDisplayCutout;
/**
* In multi-window we force show the navigation bar. Because we don't want that the surface size
@@ -47,6 +49,7 @@ public final class WindowInsets {
private boolean mSystemWindowInsetsConsumed = false;
private boolean mWindowDecorInsetsConsumed = false;
private boolean mStableInsetsConsumed = false;
+ private boolean mCutoutConsumed = false;
private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0);
@@ -59,12 +62,12 @@ public final class WindowInsets {
public static final WindowInsets CONSUMED;
static {
- CONSUMED = new WindowInsets(null, null, null, false, false);
+ CONSUMED = new WindowInsets(null, null, null, false, false, null);
}
/** @hide */
public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, Rect stableInsets,
- boolean isRound, boolean alwaysConsumeNavBar) {
+ boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
mSystemWindowInsetsConsumed = systemWindowInsets == null;
mSystemWindowInsets = mSystemWindowInsetsConsumed ? EMPTY_RECT : systemWindowInsets;
@@ -76,6 +79,9 @@ public final class WindowInsets {
mIsRound = isRound;
mAlwaysConsumeNavBar = alwaysConsumeNavBar;
+
+ mCutoutConsumed = displayCutout == null;
+ mDisplayCutout = mCutoutConsumed ? DisplayCutout.NO_CUTOUT : displayCutout;
}
/**
@@ -92,11 +98,13 @@ public final class WindowInsets {
mStableInsetsConsumed = src.mStableInsetsConsumed;
mIsRound = src.mIsRound;
mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar;
+ mDisplayCutout = src.mDisplayCutout;
+ mCutoutConsumed = src.mCutoutConsumed;
}
/** @hide */
public WindowInsets(Rect systemWindowInsets) {
- this(systemWindowInsets, null, null, false, false);
+ this(systemWindowInsets, null, null, false, false, null);
}
/**
@@ -260,9 +268,34 @@ public final class WindowInsets {
* @return true if any inset values are nonzero
*/
public boolean hasInsets() {
- return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets();
+ return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets()
+ || mDisplayCutout.hasCutout();
+ }
+
+ /**
+ * @return the display cutout
+ * @see DisplayCutout
+ * @hide pending API
+ */
+ @NonNull
+ public DisplayCutout getDisplayCutout() {
+ return mDisplayCutout;
+ }
+
+ /**
+ * Returns a copy of this WindowInsets with the cutout fully consumed.
+ *
+ * @return A modified copy of this WindowInsets
+ * @hide pending API
+ */
+ public WindowInsets consumeCutout() {
+ final WindowInsets result = new WindowInsets(this);
+ result.mDisplayCutout = DisplayCutout.NO_CUTOUT;
+ result.mCutoutConsumed = true;
+ return result;
}
+
/**
* Check if these insets have been fully consumed.
*
@@ -277,7 +310,8 @@ public final class WindowInsets {
* @return true if the insets have been fully consumed.
*/
public boolean isConsumed() {
- return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed;
+ return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed
+ && mCutoutConsumed;
}
/**
@@ -495,7 +529,9 @@ public final class WindowInsets {
public String toString() {
return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets
+ " windowDecorInsets=" + mWindowDecorInsets
- + " stableInsets=" + mStableInsets +
- (isRound() ? " round}" : "}");
+ + " stableInsets=" + mStableInsets
+ + (mDisplayCutout.hasCutout() ? " cutout=" + mDisplayCutout : "")
+ + (isRound() ? " round" : "")
+ + "}";
}
}
diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java
index eb5fc92e..905c0715 100644
--- a/android/view/WindowManager.java
+++ b/android/view/WindowManager.java
@@ -604,8 +604,10 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16;
/**
- * Window type: panel that slides out from under the status bar
- * In multiuser systems shows on all users' windows.
+ * Window type: panel that slides out from over the status bar
+ * In multiuser systems shows on all users' windows. These windows
+ * are displayed on top of the stauts bar and any {@link #TYPE_STATUS_BAR_PANEL}
+ * windows.
* @hide
*/
public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
diff --git a/android/view/WindowManagerPolicyConstants.java b/android/view/WindowManagerPolicyConstants.java
new file mode 100644
index 00000000..21943bd6
--- /dev/null
+++ b/android/view/WindowManagerPolicyConstants.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.SystemApi;
+
+/**
+ * Constants for interfacing with WindowManagerService and WindowManagerPolicyInternal.
+ * @hide
+ */
+public interface WindowManagerPolicyConstants {
+ // Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h.
+ int FLAG_WAKE = 0x00000001;
+ int FLAG_VIRTUAL = 0x00000002;
+
+ int FLAG_INJECTED = 0x01000000;
+ int FLAG_TRUSTED = 0x02000000;
+ int FLAG_FILTERED = 0x04000000;
+ int FLAG_DISABLE_KEY_REPEAT = 0x08000000;
+
+ int FLAG_INTERACTIVE = 0x20000000;
+ int FLAG_PASS_TO_USER = 0x40000000;
+
+ // Flags for IActivityManager.keyguardGoingAway()
+ int KEYGUARD_GOING_AWAY_FLAG_TO_SHADE = 1 << 0;
+ int KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS = 1 << 1;
+ int KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER = 1 << 2;
+
+ // Flags used for indicating whether the internal and/or external input devices
+ // of some type are available.
+ int PRESENCE_INTERNAL = 1 << 0;
+ int PRESENCE_EXTERNAL = 1 << 1;
+
+ /**
+ * Sticky broadcast of the current HDMI plugged state.
+ */
+ String ACTION_HDMI_PLUGGED = "android.intent.action.HDMI_PLUGGED";
+
+ /**
+ * Extra in {@link #ACTION_HDMI_PLUGGED} indicating the state: true if
+ * plugged in to HDMI, false if not.
+ */
+ String EXTRA_HDMI_PLUGGED_STATE = "state";
+
+ /**
+ * Set to {@code true} when intent was invoked from pressing the home key.
+ * @hide
+ */
+ @SystemApi
+ String EXTRA_FROM_HOME_KEY = "android.intent.extra.FROM_HOME_KEY";
+
+ // TODO: move this to a more appropriate place.
+ interface PointerEventListener {
+ /**
+ * 1. onPointerEvent will be called on the service.UiThread.
+ * 2. motionEvent will be recycled after onPointerEvent returns so if it is needed later a
+ * copy() must be made and the copy must be recycled.
+ **/
+ void onPointerEvent(MotionEvent motionEvent);
+
+ /**
+ * @see #onPointerEvent(MotionEvent)
+ **/
+ default void onPointerEvent(MotionEvent motionEvent, int displayId) {
+ if (displayId == DEFAULT_DISPLAY) {
+ onPointerEvent(motionEvent);
+ }
+ }
+ }
+
+ /** Screen turned off because of a device admin */
+ int OFF_BECAUSE_OF_ADMIN = 1;
+ /** Screen turned off because of power button */
+ int OFF_BECAUSE_OF_USER = 2;
+ /** Screen turned off because of timeout */
+ int OFF_BECAUSE_OF_TIMEOUT = 3;
+
+ int APPLICATION_LAYER = 2;
+ int APPLICATION_MEDIA_SUBLAYER = -2;
+ int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
+ int APPLICATION_PANEL_SUBLAYER = 1;
+ int APPLICATION_SUB_PANEL_SUBLAYER = 2;
+ int APPLICATION_ABOVE_SUB_PANEL_SUBLAYER = 3;
+
+ /**
+ * Convert the off reason to a human readable format.
+ */
+ static String offReasonToString(int why) {
+ switch (why) {
+ case OFF_BECAUSE_OF_ADMIN:
+ return "OFF_BECAUSE_OF_ADMIN";
+ case OFF_BECAUSE_OF_USER:
+ return "OFF_BECAUSE_OF_USER";
+ case OFF_BECAUSE_OF_TIMEOUT:
+ return "OFF_BECAUSE_OF_TIMEOUT";
+ default:
+ return Integer.toString(why);
+ }
+ }
+}
diff --git a/android/view/accessibility/AccessibilityCache.java b/android/view/accessibility/AccessibilityCache.java
index d7851171..da5a1cd6 100644
--- a/android/view/accessibility/AccessibilityCache.java
+++ b/android/view/accessibility/AccessibilityCache.java
@@ -23,8 +23,6 @@ import android.util.LongArray;
import android.util.LongSparseArray;
import android.util.SparseArray;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.util.ArrayList;
import java.util.List;
@@ -33,8 +31,7 @@ import java.util.List;
* It is updated when windows change or nodes change.
* @hide
*/
-@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public final class AccessibilityCache {
+public class AccessibilityCache {
private static final String LOG_TAG = "AccessibilityCache";
@@ -329,6 +326,8 @@ 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/AccessibilityEvent.java b/android/view/accessibility/AccessibilityEvent.java
index 5adea669..1d19a9f5 100644
--- a/android/view/accessibility/AccessibilityEvent.java
+++ b/android/view/accessibility/AccessibilityEvent.java
@@ -16,6 +16,7 @@
package android.view.accessibility;
+import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -23,6 +24,8 @@ import android.util.Pools.SynchronizedPool;
import com.android.internal.util.BitUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -709,6 +712,38 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
*/
public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+ TYPE_VIEW_CLICKED,
+ TYPE_VIEW_LONG_CLICKED,
+ TYPE_VIEW_SELECTED,
+ TYPE_VIEW_FOCUSED,
+ TYPE_VIEW_TEXT_CHANGED,
+ TYPE_WINDOW_STATE_CHANGED,
+ TYPE_NOTIFICATION_STATE_CHANGED,
+ TYPE_VIEW_HOVER_ENTER,
+ TYPE_VIEW_HOVER_EXIT,
+ TYPE_TOUCH_EXPLORATION_GESTURE_START,
+ TYPE_TOUCH_EXPLORATION_GESTURE_END,
+ TYPE_WINDOW_CONTENT_CHANGED,
+ TYPE_VIEW_SCROLLED,
+ TYPE_VIEW_TEXT_SELECTION_CHANGED,
+ TYPE_ANNOUNCEMENT,
+ TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+ TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+ TYPE_GESTURE_DETECTION_START,
+ TYPE_GESTURE_DETECTION_END,
+ TYPE_TOUCH_INTERACTION_START,
+ TYPE_TOUCH_INTERACTION_END,
+ TYPE_WINDOWS_CHANGED,
+ TYPE_VIEW_CONTEXT_CLICKED,
+ TYPE_ASSIST_READING_CONTEXT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventType {}
+
/**
* Mask for {@link AccessibilityEvent} all types.
*
@@ -741,7 +776,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
private static final SynchronizedPool<AccessibilityEvent> sPool =
new SynchronizedPool<AccessibilityEvent>(MAX_POOL_SIZE);
- private int mEventType;
+ private @EventType int mEventType;
private CharSequence mPackageName;
private long mEventTime;
int mMovementGranularity;
@@ -833,7 +868,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
*
* @return The event type.
*/
- public int getEventType() {
+ public @EventType int getEventType() {
return mEventType;
}
@@ -890,7 +925,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
*
* @throws IllegalStateException If called from an AccessibilityService.
*/
- public void setEventType(int eventType) {
+ public void setEventType(@EventType int eventType) {
enforceNotSealed();
mEventType = eventType;
}
diff --git a/android/view/accessibility/AccessibilityInteractionClient.java b/android/view/accessibility/AccessibilityInteractionClient.java
index c3d6c695..d890f329 100644
--- a/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/android/view/accessibility/AccessibilityInteractionClient.java
@@ -28,6 +28,8 @@ import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -86,6 +88,12 @@ public final class AccessibilityInteractionClient
private static final LongSparseArray<AccessibilityInteractionClient> sClients =
new LongSparseArray<>();
+ private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
+ new SparseArray<>();
+
+ private static AccessibilityCache sAccessibilityCache =
+ new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher());
+
private final AtomicInteger mInteractionIdCounter = new AtomicInteger();
private final Object mInstanceLock = new Object();
@@ -100,12 +108,6 @@ public final class AccessibilityInteractionClient
private Message mSameThreadMessage;
- private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
- new SparseArray<>();
-
- private static final AccessibilityCache sAccessibilityCache =
- new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher());
-
/**
* @return The client for the current thread.
*/
@@ -133,6 +135,50 @@ public final class AccessibilityInteractionClient
}
}
+ /**
+ * Gets a cached accessibility service connection.
+ *
+ * @param connectionId The connection id.
+ * @return The cached connection if such.
+ */
+ public static IAccessibilityServiceConnection getConnection(int connectionId) {
+ synchronized (sConnectionCache) {
+ return sConnectionCache.get(connectionId);
+ }
+ }
+
+ /**
+ * Adds a cached accessibility service connection.
+ *
+ * @param connectionId The connection id.
+ * @param connection The connection.
+ */
+ public static void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
+ synchronized (sConnectionCache) {
+ sConnectionCache.put(connectionId, connection);
+ }
+ }
+
+ /**
+ * Removes a cached accessibility service connection.
+ *
+ * @param connectionId The connection id.
+ */
+ public static void removeConnection(int connectionId) {
+ synchronized (sConnectionCache) {
+ sConnectionCache.remove(connectionId);
+ }
+ }
+
+ /**
+ * This method is only for testing. Replacing the cache is a generally terrible idea, but
+ * tests need to be able to verify this class's interactions with the cache
+ */
+ @VisibleForTesting
+ public static void setCache(AccessibilityCache cache) {
+ sAccessibilityCache = cache;
+ }
+
private AccessibilityInteractionClient() {
/* reducing constructor visibility */
}
@@ -300,7 +346,7 @@ public final class AccessibilityInteractionClient
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, bypassCache);
if (infos != null && !infos.isEmpty()) {
for (int i = 1; i < infos.size(); i++) {
infos.get(i).recycle();
@@ -356,7 +402,7 @@ public final class AccessibilityInteractionClient
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
if (infos != null) {
- finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, false);
return infos;
}
}
@@ -409,7 +455,7 @@ public final class AccessibilityInteractionClient
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
if (infos != null) {
- finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, false);
return infos;
}
}
@@ -460,7 +506,7 @@ public final class AccessibilityInteractionClient
if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false);
return info;
}
} else {
@@ -509,7 +555,7 @@ public final class AccessibilityInteractionClient
if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false);
return info;
}
} else {
@@ -731,13 +777,17 @@ public final class AccessibilityInteractionClient
*
* @param info The info.
* @param connectionId The id of the connection to the system.
+ * @param bypassCache Whether or not to bypass the cache. The node is added to the cache if
+ * this value is {@code false}
*/
private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info,
- int connectionId) {
+ int connectionId, boolean bypassCache) {
if (info != null) {
info.setConnectionId(connectionId);
info.setSealed(true);
- sAccessibilityCache.add(info);
+ if (!bypassCache) {
+ sAccessibilityCache.add(info);
+ }
}
}
@@ -746,14 +796,16 @@ public final class AccessibilityInteractionClient
*
* @param infos The {@link AccessibilityNodeInfo}s.
* @param connectionId The id of the connection to the system.
+ * @param bypassCache Whether or not to bypass the cache. The nodes are added to the cache if
+ * this value is {@code false}
*/
private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
- int connectionId) {
+ int connectionId, boolean bypassCache) {
if (infos != null) {
final int infosCount = infos.size();
for (int i = 0; i < infosCount; i++) {
AccessibilityNodeInfo info = infos.get(i);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId, bypassCache);
}
}
}
@@ -773,41 +825,6 @@ public final class AccessibilityInteractionClient
}
/**
- * Gets a cached accessibility service connection.
- *
- * @param connectionId The connection id.
- * @return The cached connection if such.
- */
- public IAccessibilityServiceConnection getConnection(int connectionId) {
- synchronized (sConnectionCache) {
- return sConnectionCache.get(connectionId);
- }
- }
-
- /**
- * Adds a cached accessibility service connection.
- *
- * @param connectionId The connection id.
- * @param connection The connection.
- */
- public void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
- synchronized (sConnectionCache) {
- sConnectionCache.put(connectionId, connection);
- }
- }
-
- /**
- * Removes a cached accessibility service connection.
- *
- * @param connectionId The connection id.
- */
- public void removeConnection(int connectionId) {
- synchronized (sConnectionCache) {
- sConnectionCache.remove(connectionId);
- }
- }
-
- /**
* Checks whether the infos are a fully connected tree with no duplicates.
*
* @param infos The result list to check.
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 35f6acba..0375635f 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -24,6 +24,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -187,6 +188,7 @@ public final class AccessibilityManager {
*
* @hide
*/
+ @TestApi
public interface AccessibilityServicesStateChangeListener {
/**
@@ -452,6 +454,18 @@ public final class AccessibilityManager {
}
/**
+ * Returns whether there are observers registered for this event type. If
+ * this method returns false you shuold not generate events of this type
+ * to conserve resources.
+ *
+ * @param type The event type.
+ * @return Whether the event is being observed.
+ */
+ public boolean isObservedEventType(@AccessibilityEvent.EventType int type) {
+ return mIsEnabled && (mRelevantEventTypes & type) != 0;
+ }
+
+ /**
* Requests feedback interruption from all accessibility services.
*/
public void interrupt() {
@@ -683,6 +697,7 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
* @hide
*/
+ @TestApi
public void addAccessibilityServicesStateChangeListener(
@NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
synchronized (mLock) {
@@ -698,6 +713,7 @@ public final class AccessibilityManager {
*
* @hide
*/
+ @TestApi
public void removeAccessibilityServicesStateChangeListener(
@NonNull AccessibilityServicesStateChangeListener listener) {
// Final CopyOnWriteArrayList - no lock needed.
diff --git a/android/view/accessibility/AccessibilityNodeInfo.java b/android/view/accessibility/AccessibilityNodeInfo.java
index 9bdd3ffc..faea9200 100644
--- a/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/android/view/accessibility/AccessibilityNodeInfo.java
@@ -635,6 +635,8 @@ public class AccessibilityNodeInfo implements Parcelable {
private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000;
+ private static final int BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE = 0x0080000;
+
private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000;
/**
@@ -2321,6 +2323,37 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Returns whether the node is explicitly marked as a focusable unit by a screen reader. Note
+ * that {@code false} indicates that it is not explicitly marked, not that the node is not
+ * a focusable unit. Screen readers should generally used other signals, such as
+ * {@link #isFocusable()}, or the presence of text in a node, to determine what should receive
+ * focus.
+ *
+ * @return {@code true} if the node is specifically marked as a focusable unit for screen
+ * readers, {@code false} otherwise.
+ *
+ * @see View#isScreenReaderFocusable()
+ */
+ public boolean isScreenReaderFocusable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE);
+ }
+
+ /**
+ * Sets whether the node should be considered a focusable unit by a screen reader.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param screenReaderFocusable {@code true} if the node is a focusable unit for screen readers,
+ * {@code false} otherwise.
+ */
+ public void setScreenReaderFocusable(boolean screenReaderFocusable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE, screenReaderFocusable);
+ }
+
+ /**
* Returns whether the node's text represents a hint for the user to enter text. It should only
* be {@code true} if the node has editable text.
*
diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java
index 9241ec00..547e0db9 100644
--- a/android/view/autofill/AutofillManager.java
+++ b/android/view/autofill/AutofillManager.java
@@ -908,6 +908,7 @@ public final class AutofillManager {
}
synchronized (mLock) {
if (mSaveOnFinish) {
+ if (sDebug) Log.d(TAG, "Committing session on finish() as requested by service");
commitLocked();
} else {
if (sDebug) Log.d(TAG, "Cancelling session on finish() as requested by service");
@@ -955,6 +956,7 @@ public final class AutofillManager {
* methods such as {@link android.app.Activity#finish()}.
*/
public void cancel() {
+ if (sVerbose) Log.v(TAG, "cancel() called by app");
if (!hasAutofillFeature()) {
return;
}
diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java
index 2779aa2d..f675c355 100644
--- a/android/view/textclassifier/TextClassification.java
+++ b/android/view/textclassifier/TextClassification.java
@@ -31,6 +31,7 @@ import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
/**
* Information for generating a widget to handle classified text.
@@ -42,7 +43,7 @@ import java.util.List;
*
* <pre>{@code
* // Called preferably outside the UiThread.
- * TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ * TextClassification classification = textClassifier.classifyText(allText, 10, 25);
*
* // Called on the UiThread.
* Button button = new Button(context);
@@ -55,7 +56,7 @@ import java.util.List;
*
* <pre>{@code
* // Called preferably outside the UiThread.
- * final TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ * final TextClassification classification = textClassifier.classifyText(allText, 10, 25);
*
* // Called on the UiThread.
* view.startActionMode(new ActionMode.Callback() {
@@ -281,8 +282,8 @@ public final class TextClassification {
@Override
public String toString() {
- return String.format("TextClassification {"
- + "text=%s, entities=%s, labels=%s, intents=%s}",
+ return String.format(Locale.US,
+ "TextClassification {text=%s, entities=%s, labels=%s, intents=%s}",
mText, mEntityConfidence, mLabels, mIntents);
}
@@ -421,7 +422,7 @@ public final class TextClassification {
}
/**
- * Ensures that we have at we have storage for the default action.
+ * Ensures that we have storage for the default action.
*/
private void ensureDefaultActionAvailable() {
if (mIntents.isEmpty()) mIntents.add(null);
@@ -441,7 +442,7 @@ public final class TextClassification {
}
/**
- * TextClassification optional input parameters.
+ * Optional input parameters for generating TextClassification.
*/
public static final class Options {
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
index aeb84897..5aaa5ad1 100644
--- a/android/view/textclassifier/TextClassifier.java
+++ b/android/view/textclassifier/TextClassifier.java
@@ -23,6 +23,8 @@ import android.annotation.StringDef;
import android.annotation.WorkerThread;
import android.os.LocaleList;
+import com.android.internal.util.Preconditions;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -37,8 +39,7 @@ public interface TextClassifier {
/** @hide */
String DEFAULT_LOG_TAG = "TextClassifierImpl";
- /** @hide */
- String TYPE_UNKNOWN = ""; // TODO: Make this public API.
+ String TYPE_UNKNOWN = "";
String TYPE_OTHER = "other";
String TYPE_EMAIL = "email";
String TYPE_PHONE = "phone";
@@ -70,6 +71,8 @@ public interface TextClassifier {
*
* @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
* selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
+ *
+ * @see #suggestSelection(CharSequence, int, int)
*/
@WorkerThread
@NonNull
@@ -78,13 +81,46 @@ public interface TextClassifier {
@IntRange(from = 0) int selectionStartIndex,
@IntRange(from = 0) int selectionEndIndex,
@Nullable TextSelection.Options options) {
+ Utils.validateInput(text, selectionStartIndex, selectionEndIndex);
return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
}
/**
+ * Returns suggested text selection start and end indices, recognized entity types, and their
+ * associated confidence scores. The entity types are ordered from highest to lowest scoring.
+ *
+ * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
+ * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method
+ * calls this method, a stack overflow error will happen.
+ *
+ * @param text text providing context for the selected text (which is specified
+ * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
+ * @param selectionStartIndex start index of the selected part of text
+ * @param selectionEndIndex end index of the selected part of text
+ *
+ * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
+ * selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
+ *
* @see #suggestSelection(CharSequence, int, int, TextSelection.Options)
*/
- // TODO: Consider deprecating (b/68846316)
+ @WorkerThread
+ @NonNull
+ default TextSelection suggestSelection(
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int selectionStartIndex,
+ @IntRange(from = 0) int selectionEndIndex) {
+ return suggestSelection(text, selectionStartIndex, selectionEndIndex,
+ (TextSelection.Options) null);
+ }
+
+ /**
+ * See {@link #suggestSelection(CharSequence, int, int)} or
+ * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}.
+ *
+ * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
+ * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method
+ * calls this method, a stack overflow error will happen.
+ */
@WorkerThread
@NonNull
default TextSelection suggestSelection(
@@ -92,7 +128,10 @@ public interface TextClassifier {
@IntRange(from = 0) int selectionStartIndex,
@IntRange(from = 0) int selectionEndIndex,
@Nullable LocaleList defaultLocales) {
- return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+ final TextSelection.Options options = (defaultLocales != null)
+ ? new TextSelection.Options().setDefaultLocales(defaultLocales)
+ : null;
+ return suggestSelection(text, selectionStartIndex, selectionEndIndex, options);
}
/**
@@ -107,6 +146,8 @@ public interface TextClassifier {
*
* @throws IllegalArgumentException if text is null; startIndex is negative;
* endIndex is greater than text.length() or not greater than startIndex
+ *
+ * @see #classifyText(CharSequence, int, int)
*/
@WorkerThread
@NonNull
@@ -115,13 +156,45 @@ public interface TextClassifier {
@IntRange(from = 0) int startIndex,
@IntRange(from = 0) int endIndex,
@Nullable TextClassification.Options options) {
+ Utils.validateInput(text, startIndex, endIndex);
return TextClassification.EMPTY;
}
/**
+ * Classifies the specified text and returns a {@link TextClassification} object that can be
+ * used to generate a widget for handling the classified text.
+ *
+ * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
+ * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method
+ * calls this method, a stack overflow error will happen.
+ *
+ * @param text text providing context for the text to classify (which is specified
+ * by the sub sequence starting at startIndex and ending at endIndex)
+ * @param startIndex start index of the text to classify
+ * @param endIndex end index of the text to classify
+ *
+ * @throws IllegalArgumentException if text is null; startIndex is negative;
+ * endIndex is greater than text.length() or not greater than startIndex
+ *
* @see #classifyText(CharSequence, int, int, TextClassification.Options)
*/
- // TODO: Consider deprecating (b/68846316)
+ @WorkerThread
+ @NonNull
+ default TextClassification classifyText(
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int startIndex,
+ @IntRange(from = 0) int endIndex) {
+ return classifyText(text, startIndex, endIndex, (TextClassification.Options) null);
+ }
+
+ /**
+ * See {@link #classifyText(CharSequence, int, int, TextClassification.Options)} or
+ * {@link #classifyText(CharSequence, int, int)}.
+ *
+ * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
+ * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method
+ * calls this method, a stack overflow error will happen.
+ */
@WorkerThread
@NonNull
default TextClassification classifyText(
@@ -129,7 +202,10 @@ public interface TextClassifier {
@IntRange(from = 0) int startIndex,
@IntRange(from = 0) int endIndex,
@Nullable LocaleList defaultLocales) {
- return TextClassification.EMPTY;
+ final TextClassification.Options options = (defaultLocales != null)
+ ? new TextClassification.Options().setDefaultLocales(defaultLocales)
+ : null;
+ return classifyText(text, startIndex, endIndex, options);
}
/**
@@ -137,17 +213,39 @@ public interface TextClassifier {
* information.
*
* @param text the text to generate annotations for
- * @param options configuration for link generation. If null, defaults will be used.
+ * @param options configuration for link generation
*
* @throws IllegalArgumentException if text is null
+ *
+ * @see #generateLinks(CharSequence)
*/
@WorkerThread
default TextLinks generateLinks(
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
+ Utils.validateInput(text);
return new TextLinks.Builder(text.toString()).build();
}
/**
+ * Returns a {@link TextLinks} that may be applied to the text to annotate it with links
+ * information.
+ *
+ * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls
+ * {@link #generateLinks(CharSequence, TextLinks.Options)}. If that method calls this method,
+ * a stack overflow error will happen.
+ *
+ * @param text the text to generate annotations for
+ *
+ * @throws IllegalArgumentException if text is null
+ *
+ * @see #generateLinks(CharSequence, TextLinks.Options)
+ */
+ @WorkerThread
+ default TextLinks generateLinks(@NonNull CharSequence text) {
+ return generateLinks(text, null);
+ }
+
+ /**
* Logs a TextClassifier event.
*
* @param source the text classifier used to generate this event
@@ -164,4 +262,38 @@ public interface TextClassifier {
default TextClassifierConstants getSettings() {
return TextClassifierConstants.DEFAULT;
}
+
+
+ /**
+ * Utility functions for TextClassifier methods.
+ *
+ * <ul>
+ * <li>Provides validation of input parameters to TextClassifier methods
+ * </ul>
+ *
+ * Intended to be used only in this package.
+ * @hide
+ */
+ final class Utils {
+
+ /**
+ * @throws IllegalArgumentException if text is null; startIndex is negative;
+ * endIndex is greater than text.length() or is not greater than startIndex;
+ * options is null
+ */
+ static void validateInput(
+ @NonNull CharSequence text, int startIndex, int endIndex) {
+ Preconditions.checkArgument(text != null);
+ Preconditions.checkArgument(startIndex >= 0);
+ Preconditions.checkArgument(endIndex <= text.length());
+ Preconditions.checkArgument(endIndex > startIndex);
+ }
+
+ /**
+ * @throws IllegalArgumentException if text is null or options is null
+ */
+ static void validateInput(@NonNull CharSequence text) {
+ Preconditions.checkArgument(text != null);
+ }
+ }
}
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
index 2ad6e02c..df5e35f0 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -90,8 +90,8 @@ final class TextClassifierImpl implements TextClassifier {
@Override
public TextSelection suggestSelection(
@NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
- @Nullable TextSelection.Options options) {
- validateInput(text, selectionStartIndex, selectionEndIndex);
+ @NonNull TextSelection.Options options) {
+ Utils.validateInput(text, selectionStartIndex, selectionEndIndex);
try {
if (text.length() > 0) {
final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
@@ -141,18 +141,10 @@ final class TextClassifierImpl implements TextClassifier {
}
@Override
- public TextSelection suggestSelection(
- @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
- @Nullable LocaleList defaultLocales) {
- return suggestSelection(text, selectionStartIndex, selectionEndIndex,
- new TextSelection.Options().setDefaultLocales(defaultLocales));
- }
-
- @Override
public TextClassification classifyText(
@NonNull CharSequence text, int startIndex, int endIndex,
- @Nullable TextClassification.Options options) {
- validateInput(text, startIndex, endIndex);
+ @NonNull TextClassification.Options options) {
+ Utils.validateInput(text, startIndex, endIndex);
try {
if (text.length() > 0) {
final String string = text.toString();
@@ -176,17 +168,9 @@ final class TextClassifierImpl implements TextClassifier {
}
@Override
- public TextClassification classifyText(
- @NonNull CharSequence text, int startIndex, int endIndex,
- @Nullable LocaleList defaultLocales) {
- return classifyText(text, startIndex, endIndex,
- new TextClassification.Options().setDefaultLocales(defaultLocales));
- }
-
- @Override
public TextLinks generateLinks(
- @NonNull CharSequence text, @Nullable TextLinks.Options options) {
- Preconditions.checkNotNull(text);
+ @NonNull CharSequence text, @NonNull TextLinks.Options options) {
+ Utils.validateInput(text);
final String textString = text.toString();
final TextLinks.Builder builder = new TextLinks.Builder(textString);
try {
@@ -486,17 +470,6 @@ final class TextClassifierImpl implements TextClassifier {
}
/**
- * @throws IllegalArgumentException if text is null; startIndex is negative;
- * endIndex is greater than text.length() or is not greater than startIndex
- */
- private static void validateInput(@NonNull CharSequence text, int startIndex, int endIndex) {
- Preconditions.checkArgument(text != null);
- Preconditions.checkArgument(startIndex >= 0);
- Preconditions.checkArgument(endIndex <= text.length());
- Preconditions.checkArgument(endIndex > startIndex);
- }
-
- /**
* Creates intents based on the classification type.
*/
private static final class IntentFactory {
diff --git a/android/view/textclassifier/TextLinks.java b/android/view/textclassifier/TextLinks.java
index f3cc827f..76748d2b 100644
--- a/android/view/textclassifier/TextLinks.java
+++ b/android/view/textclassifier/TextLinks.java
@@ -161,39 +161,28 @@ public final class TextLinks {
* Optional input parameters for generating TextLinks.
*/
public static final class Options {
- private final LocaleList mLocaleList;
- private Options(LocaleList localeList) {
- this.mLocaleList = localeList;
- }
+ private LocaleList mDefaultLocales;
/**
- * Builder to construct Options.
+ * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
+ * the provided text. If no locale preferences exist, set this to null or an empty
+ * locale list.
*/
- public static final class Builder {
- private LocaleList mLocaleList;
-
- /**
- * Sets the LocaleList to use.
- *
- * @return this Builder.
- */
- public Builder setLocaleList(@Nullable LocaleList localeList) {
- this.mLocaleList = localeList;
- return this;
- }
-
- /**
- * Builds the Options object.
- */
- public Options build() {
- return new Options(mLocaleList);
- }
+ public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+ mDefaultLocales = defaultLocales;
+ return this;
}
- public @Nullable LocaleList getDefaultLocales() {
- return mLocaleList;
+
+ /**
+ * @return ordered list of locale preferences that can be used to disambiguate
+ * the provided text.
+ */
+ @Nullable
+ public LocaleList getDefaultLocales() {
+ return mDefaultLocales;
}
- };
+ }
/**
* A function to create spans from TextLinks.
@@ -204,13 +193,10 @@ public final class TextLinks {
* @hide
*/
public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY =
- new Function<TextLink, ClickableSpan>() {
- @Override
- public ClickableSpan apply(TextLink textLink) {
- // TODO: Implement.
- throw new UnsupportedOperationException("Not yet implemented");
- }
- };
+ textLink -> {
+ // TODO: Implement.
+ throw new UnsupportedOperationException("Not yet implemented");
+ };
/**
* A builder to construct a TextLinks instance.
diff --git a/android/view/textclassifier/TextSelection.java b/android/view/textclassifier/TextSelection.java
index 0a67954a..480b27a7 100644
--- a/android/view/textclassifier/TextSelection.java
+++ b/android/view/textclassifier/TextSelection.java
@@ -26,6 +26,7 @@ import android.view.textclassifier.TextClassifier.EntityType;
import com.android.internal.util.Preconditions;
import java.util.List;
+import java.util.Locale;
/**
* Information about where text selection should be.
@@ -114,8 +115,8 @@ public final class TextSelection {
@Override
public String toString() {
- return String.format("TextSelection {%d, %d, %s}",
- mStartIndex, mEndIndex, mEntityConfidence);
+ return String.format(Locale.US,
+ "TextSelection {%d, %d, %s}", mStartIndex, mEndIndex, mEntityConfidence);
}
/**
@@ -185,7 +186,7 @@ public final class TextSelection {
}
/**
- * TextSelection optional input parameters.
+ * Optional input parameters for generating TextSelection.
*/
public static final class Options {
diff --git a/android/webkit/UserPackage.java b/android/webkit/UserPackage.java
index 4cf34618..63519a65 100644
--- a/android/webkit/UserPackage.java
+++ b/android/webkit/UserPackage.java
@@ -34,6 +34,8 @@ public class UserPackage {
private final UserInfo mUserInfo;
private final PackageInfo mPackageInfo;
+ public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.O_MR1;
+
public UserPackage(UserInfo user, PackageInfo packageInfo) {
this.mUserInfo = user;
this.mPackageInfo = packageInfo;
@@ -83,7 +85,7 @@ public class UserPackage {
* supported by the current framework version.
*/
public static boolean hasCorrectTargetSdkVersion(PackageInfo packageInfo) {
- return packageInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O_MR1;
+ return packageInfo.applicationInfo.targetSdkVersion >= MINIMUM_SUPPORTED_SDK;
}
public UserInfo getUserInfo() {
diff --git a/android/webkit/WebSettings.java b/android/webkit/WebSettings.java
index 203de9c2..e4937392 100644
--- a/android/webkit/WebSettings.java
+++ b/android/webkit/WebSettings.java
@@ -29,10 +29,10 @@ import java.lang.annotation.Target;
/**
* Manages settings state for a WebView. When a WebView is first created, it
* obtains a set of default settings. These default settings will be returned
- * from any getter call. A WebSettings object obtained from
- * WebView.getSettings() is tied to the life of the WebView. If a WebView has
- * been destroyed, any method call on WebSettings will throw an
- * IllegalStateException.
+ * from any getter call. A {@code WebSettings} object obtained from
+ * {@link WebView#getSettings()} is tied to the life of the WebView. If a WebView has
+ * been destroyed, any method call on {@code WebSettings} will throw an
+ * {@link IllegalStateException}.
*/
// This is an abstract base class: concrete WebViewProviders must
// create a class derived from this, and return an instance of it in the
@@ -41,13 +41,13 @@ public abstract class WebSettings {
/**
* Enum for controlling the layout of html.
* <ul>
- * <li>NORMAL means no rendering changes. This is the recommended choice for maximum
+ * <li>{@code NORMAL} means no rendering changes. This is the recommended choice for maximum
* compatibility across different platforms and Android versions.</li>
- * <li>SINGLE_COLUMN moves all content into one column that is the width of the
+ * <li>{@code SINGLE_COLUMN} moves all content into one column that is the width of the
* view.</li>
- * <li>NARROW_COLUMNS makes all columns no wider than the screen if possible. Only use
+ * <li>{@code NARROW_COLUMNS} makes all columns no wider than the screen if possible. Only use
* this for API levels prior to {@link android.os.Build.VERSION_CODES#KITKAT}.</li>
- * <li>TEXT_AUTOSIZING boosts font size of paragraphs based on heuristics to make
+ * <li>{@code TEXT_AUTOSIZING} boosts font size of paragraphs based on heuristics to make
* the text readable when viewing a wide-viewport layout in the overview mode.
* It is recommended to enable zoom support {@link #setSupportZoom} when
* using this mode. Supported from API level
@@ -98,9 +98,9 @@ public abstract class WebSettings {
/**
* Enum for specifying the WebView's desired density.
* <ul>
- * <li>FAR makes 100% looking like in 240dpi</li>
- * <li>MEDIUM makes 100% looking like in 160dpi</li>
- * <li>CLOSE makes 100% looking like in 120dpi</li>
+ * <li>{@code FAR} makes 100% looking like in 240dpi</li>
+ * <li>{@code MEDIUM} makes 100% looking like in 160dpi</li>
+ * <li>{@code CLOSE} makes 100% looking like in 120dpi</li>
* </ul>
*/
public enum ZoomDensity {
@@ -652,7 +652,7 @@ public abstract class WebSettings {
* true, {@link WebChromeClient#onCreateWindow} must be implemented by the
* host application. The default is {@code false}.
*
- * @param support whether to suport multiple windows
+ * @param support whether to support multiple windows
*/
public abstract void setSupportMultipleWindows(boolean support);
@@ -665,7 +665,7 @@ public abstract class WebSettings {
public abstract boolean supportMultipleWindows();
/**
- * Sets the underlying layout algorithm. This will cause a relayout of the
+ * Sets the underlying layout algorithm. This will cause a re-layout of the
* WebView. The default is {@link LayoutAlgorithm#NARROW_COLUMNS}.
*
* @param l the layout algorithm to use, as a {@link LayoutAlgorithm} value
@@ -1198,7 +1198,7 @@ public abstract class WebSettings {
/**
* Tells JavaScript to open windows automatically. This applies to the
- * JavaScript function window.open(). The default is {@code false}.
+ * JavaScript function {@code window.open()}. The default is {@code false}.
*
* @param flag {@code true} if JavaScript can open windows automatically
*/
@@ -1208,7 +1208,7 @@ public abstract class WebSettings {
* Gets whether JavaScript can open windows automatically.
*
* @return {@code true} if JavaScript can open windows automatically during
- * window.open()
+ * {@code window.open()}
* @see #setJavaScriptCanOpenWindowsAutomatically
*/
public abstract boolean getJavaScriptCanOpenWindowsAutomatically();
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index 665d694e..6f992548 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -2758,7 +2758,9 @@ public class WebView extends AbsoluteLayout
* <p>For example, an HTML form with 2 fields for username and password:
*
* <pre class="prettyprint">
+ * &lt;label&gt;Username:&lt;/label&gt;
* &lt;input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username"&gt;
+ * &lt;label&gt;Password:&lt;/label&gt;
* &lt;input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password"&gt;
* </pre>
*
@@ -2772,6 +2774,7 @@ public class WebView extends AbsoluteLayout
* username.setHtmlInfo(username.newHtmlInfoBuilder("input")
* .addAttribute("type", "text")
* .addAttribute("name", "username")
+ * .addAttribute("label", "Username:")
* .build());
* username.setHint("Email or username");
* username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
@@ -2785,6 +2788,7 @@ public class WebView extends AbsoluteLayout
* password.setHtmlInfo(password.newHtmlInfoBuilder("input")
* .addAttribute("type", "password")
* .addAttribute("name", "password")
+ * .addAttribute("label", "Password:")
* .build());
* password.setHint("Password");
* password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
diff --git a/android/webkit/WebViewClient.java b/android/webkit/WebViewClient.java
index 517ad07c..46c39834 100644
--- a/android/webkit/WebViewClient.java
+++ b/android/webkit/WebViewClient.java
@@ -364,13 +364,13 @@ public class WebViewClient {
}
/**
- * Notify the host application to handle a SSL client certificate
- * request. The host application is responsible for showing the UI
- * if desired and providing the keys. There are three ways to
- * respond: proceed(), cancel() or ignore(). Webview stores the response
- * in memory (for the life of the application) if proceed() or cancel() is
- * called and does not call {@code onReceivedClientCertRequest()} again for the
- * same host and port pair. Webview does not store the response if ignore()
+ * Notify the host application to handle a SSL client certificate request. The host application
+ * is responsible for showing the UI if desired and providing the keys. There are three ways to
+ * respond: {@link ClientCertRequest#proceed}, {@link ClientCertRequest#cancel}, or {@link
+ * ClientCertRequest#ignore}. Webview stores the response in memory (for the life of the
+ * application) if {@link ClientCertRequest#proceed} or {@link ClientCertRequest#cancel} is
+ * called and does not call {@code onReceivedClientCertRequest()} again for the same host and
+ * port pair. Webview does not store the response if {@link ClientCertRequest#ignore}
* is called. Note that, multiple layers in chromium network stack might be
* caching the responses, so the behavior for ignore is only a best case
* effort.
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index d477ffdf..05cba1e5 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -119,7 +119,6 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.EditableInputConnection;
-import com.android.internal.widget.Magnifier;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -195,6 +194,27 @@ public class Editor {
private final boolean mHapticTextHandleEnabled;
private final Magnifier mMagnifier;
+ private final Runnable mUpdateMagnifierRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mMagnifier.update();
+ }
+ };
+ // Update the magnifier contents whenever anything in the view hierarchy is updated.
+ // Note: this only captures UI thread-visible changes, so it's a known issue that an animating
+ // VectorDrawable or Ripple animation will not trigger capture, since they're owned by
+ // RenderThread.
+ private final ViewTreeObserver.OnDrawListener mMagnifierOnDrawListener =
+ new ViewTreeObserver.OnDrawListener() {
+ @Override
+ public void onDraw() {
+ if (mMagnifier != null) {
+ // Posting the method will ensure that updating the magnifier contents will
+ // happen right after the rendering of the current frame.
+ mTextView.post(mUpdateMagnifierRunnable);
+ }
+ }
+ };
// Used to highlight a word when it is corrected by the IME
private CorrectionHighlighter mCorrectionHighlighter;
@@ -415,15 +435,21 @@ public class Editor {
}
final ViewTreeObserver observer = mTextView.getViewTreeObserver();
- // No need to create the controller.
- // The get method will add the listener on controller creation.
- if (mInsertionPointCursorController != null) {
- observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
- }
- if (mSelectionModifierCursorController != null) {
- mSelectionModifierCursorController.resetTouchOffsets();
- observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
+ if (observer.isAlive()) {
+ // No need to create the controller.
+ // The get method will add the listener on controller creation.
+ if (mInsertionPointCursorController != null) {
+ observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
+ }
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.resetTouchOffsets();
+ observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
+ }
+ if (FLAG_USE_MAGNIFIER) {
+ observer.addOnDrawListener(mMagnifierOnDrawListener);
+ }
}
+
updateSpellCheckSpans(0, mTextView.getText().length(),
true /* create the spell checker if needed */);
@@ -472,6 +498,13 @@ public class Editor {
mSpellChecker = null;
}
+ if (FLAG_USE_MAGNIFIER) {
+ final ViewTreeObserver observer = mTextView.getViewTreeObserver();
+ if (observer.isAlive()) {
+ observer.removeOnDrawListener(mMagnifierOnDrawListener);
+ }
+ }
+
hideCursorAndSpanControllers();
stopTextActionModeWithPreservingSelection();
}
diff --git a/android/widget/Magnifier.java b/android/widget/Magnifier.java
new file mode 100644
index 00000000..bd48f455
--- /dev/null
+++ b/android/widget/Magnifier.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+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.os.Handler;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.PixelCopy;
+import android.view.Surface;
+import android.view.SurfaceView;
+import android.view.View;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Android magnifier widget. Can be used by any view which is attached to a window.
+ */
+@UiThread
+public final class Magnifier {
+ // Use this to specify that a previous configuration value does not exist.
+ private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
+ // The view to which this magnifier is attached.
+ private final View mView;
+ // The window containing the magnifier.
+ private final PopupWindow mWindow;
+ // The center coordinates of the window containing the magnifier.
+ private final Point mWindowCoords = new Point();
+ // The width of the window containing the magnifier.
+ private final int mWindowWidth;
+ // The height of the window containing the magnifier.
+ private final int mWindowHeight;
+ // The bitmap used to display the contents of the magnifier.
+ private final Bitmap mBitmap;
+ // The center coordinates of the content that is to be magnified.
+ private final Point mCenterZoomCoords = new Point();
+ // The callback of the pixel copy request will be invoked on this Handler when
+ // the copy is finished.
+ private final Handler mPixelCopyHandler = Handler.getMain();
+ // Current magnification scale.
+ private final float mZoomScale;
+ // Variables holding previous states, used for detecting redundant calls and invalidation.
+ private final Point mPrevStartCoordsInSurface = new Point(
+ NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE);
+ private final PointF mPrevPosInView = new PointF(
+ NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE);
+ private final Rect mPixelCopyRequestRect = new Rect();
+
+ /**
+ * Initializes a magnifier.
+ *
+ * @param view the view for which this magnifier is attached
+ */
+ public Magnifier(@NonNull View view) {
+ mView = Preconditions.checkNotNull(view);
+ final Context context = mView.getContext();
+ final float elevation = context.getResources().getDimension(
+ com.android.internal.R.dimen.magnifier_elevation);
+ final View content = LayoutInflater.from(context).inflate(
+ com.android.internal.R.layout.magnifier, null);
+ content.findViewById(com.android.internal.R.id.magnifier_inner).setClipToOutline(true);
+ mWindowWidth = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.magnifier_width);
+ mWindowHeight = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.magnifier_height);
+ mZoomScale = context.getResources().getFloat(
+ com.android.internal.R.dimen.magnifier_zoom_scale);
+
+ mWindow = new PopupWindow(context);
+ mWindow.setContentView(content);
+ mWindow.setWidth(mWindowWidth);
+ mWindow.setHeight(mWindowHeight);
+ mWindow.setElevation(elevation);
+ mWindow.setTouchable(false);
+ mWindow.setBackgroundDrawable(null);
+
+ final int bitmapWidth = Math.round(mWindowWidth / mZoomScale);
+ final int bitmapHeight = Math.round(mWindowHeight / mZoomScale);
+ mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+ getImageView().setImageBitmap(mBitmap);
+ }
+
+ /**
+ * Shows the magnifier on the screen.
+ *
+ * @param xPosInView horizontal coordinate of the center point of the magnifier source relative
+ * to the view. The lower end is clamped to 0 and the higher end is clamped to the view
+ * width.
+ * @param yPosInView vertical coordinate of the center point of the magnifier source
+ * relative to the view. The lower end is clamped to 0 and the higher end is clamped to
+ * the view height.
+ */
+ public void show(@FloatRange(from = 0) float xPosInView,
+ @FloatRange(from = 0) float yPosInView) {
+ xPosInView = Math.max(0, Math.min(xPosInView, mView.getWidth()));
+ yPosInView = Math.max(0, Math.min(yPosInView, mView.getHeight()));
+
+ configureCoordinates(xPosInView, yPosInView);
+
+ // Clamp startX value to avoid distorting the rendering of the magnifier content.
+ final int startX = Math.max(0, Math.min(
+ mCenterZoomCoords.x - mBitmap.getWidth() / 2,
+ mView.getWidth() - mBitmap.getWidth()));
+ final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
+
+ if (startX != mPrevStartCoordsInSurface.x || startY != mPrevStartCoordsInSurface.y) {
+ performPixelCopy(startX, startY);
+
+ mPrevPosInView.x = xPosInView;
+ mPrevPosInView.y = yPosInView;
+
+ if (mWindow.isShowing()) {
+ mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
+ mWindow.getHeight());
+ } else {
+ mWindow.showAtLocation(mView, Gravity.NO_GRAVITY, mWindowCoords.x, mWindowCoords.y);
+ }
+ }
+ }
+
+ /**
+ * Dismisses the magnifier from the screen. Calling this on a dismissed magnifier is a no-op.
+ */
+ public void dismiss() {
+ mWindow.dismiss();
+ }
+
+ /**
+ * Forces the magnifier to update its content. It uses the previous coordinates passed to
+ * {@link #show(float, float)}. This only happens if the magnifier is currently showing.
+ *
+ * @hide
+ */
+ public void update() {
+ if (mWindow.isShowing()) {
+ // Update the contents shown in the magnifier.
+ performPixelCopy(mPrevStartCoordsInSurface.x, mPrevStartCoordsInSurface.y);
+ }
+ }
+
+ private void configureCoordinates(float xPosInView, float yPosInView) {
+ final float posX;
+ final float posY;
+
+ if (mView instanceof SurfaceView) {
+ // No offset required if the backing Surface matches the size of the SurfaceView.
+ posX = xPosInView;
+ posY = yPosInView;
+ } else {
+ final int[] coordinatesInSurface = new int[2];
+ mView.getLocationInSurface(coordinatesInSurface);
+ posX = xPosInView + coordinatesInSurface[0];
+ posY = yPosInView + coordinatesInSurface[1];
+ }
+
+ mCenterZoomCoords.x = Math.round(posX);
+ mCenterZoomCoords.y = Math.round(posY);
+
+ final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.magnifier_offset);
+ mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2;
+ mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset;
+ }
+
+ private void performPixelCopy(final int startXInSurface, final int startYInSurface) {
+ final Surface surface = getValidViewSurface();
+ if (surface != null) {
+ mPixelCopyRequestRect.set(startXInSurface, startYInSurface,
+ startXInSurface + mBitmap.getWidth(), startYInSurface + mBitmap.getHeight());
+
+ PixelCopy.request(surface, mPixelCopyRequestRect, mBitmap,
+ result -> {
+ getImageView().invalidate();
+ mPrevStartCoordsInSurface.x = startXInSurface;
+ mPrevStartCoordsInSurface.y = startYInSurface;
+ },
+ mPixelCopyHandler);
+ }
+ }
+
+ @Nullable
+ private Surface getValidViewSurface() {
+ final Surface surface;
+ if (mView instanceof SurfaceView) {
+ surface = ((SurfaceView) mView).getHolder().getSurface();
+ } else if (mView.getViewRootImpl() != null) {
+ surface = mView.getViewRootImpl().mSurface;
+ } else {
+ surface = null;
+ }
+
+ return (surface != null && surface.isValid()) ? surface : null;
+ }
+
+ private ImageView getImageView() {
+ return mWindow.getContentView().findViewById(
+ com.android.internal.R.id.magnifier_image);
+ }
+}
diff --git a/android/widget/NumberPicker.java b/android/widget/NumberPicker.java
index 4d3189ef..b3792806 100644
--- a/android/widget/NumberPicker.java
+++ b/android/widget/NumberPicker.java
@@ -1952,7 +1952,8 @@ public class NumberPicker extends LinearLayout {
CharSequence beforeText = mInputText.getText();
if (!text.equals(beforeText.toString())) {
mInputText.setText(text);
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
mInputText.onInitializeAccessibilityEvent(event);
@@ -2612,7 +2613,7 @@ public class NumberPicker extends LinearLayout {
}
private void sendAccessibilityEventForVirtualText(int eventType) {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
mInputText.onInitializeAccessibilityEvent(event);
mInputText.onPopulateAccessibilityEvent(event);
@@ -2623,7 +2624,7 @@ public class NumberPicker extends LinearLayout {
private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType,
String text) {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
event.setClassName(Button.class.getName());
event.setPackageName(mContext.getPackageName());
diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java
index e3309161..a2c55b09 100644
--- a/android/widget/RemoteViews.java
+++ b/android/widget/RemoteViews.java
@@ -90,6 +90,31 @@ import java.util.concurrent.Executor;
* another process. The hierarchy is inflated from a layout resource
* file, and this class provides some basic operations for modifying
* the content of the inflated hierarchy.
+ *
+ * <p>{@code RemoteViews} is limited to support for the following layouts:</p>
+ * <ul>
+ * <li>{@link android.widget.AdapterViewFlipper}</li>
+ * <li>{@link android.widget.FrameLayout}</li>
+ * <li>{@link android.widget.GridLayout}</li>
+ * <li>{@link android.widget.GridView}</li>
+ * <li>{@link android.widget.LinearLayout}</li>
+ * <li>{@link android.widget.ListView}</li>
+ * <li>{@link android.widget.RelativeLayout}</li>
+ * <li>{@link android.widget.StackView}</li>
+ * <li>{@link android.widget.ViewFlipper}</li>
+ * </ul>
+ * <p>And the following widgets:</p>
+ * <ul>
+ * <li>{@link android.widget.AnalogClock}</li>
+ * <li>{@link android.widget.Button}</li>
+ * <li>{@link android.widget.Chronometer}</li>
+ * <li>{@link android.widget.ImageButton}</li>
+ * <li>{@link android.widget.ImageView}</li>
+ * <li>{@link android.widget.ProgressBar}</li>
+ * <li>{@link android.widget.TextClock}</li>
+ * <li>{@link android.widget.TextView}</li>
+ * </ul>
+ * <p>Descendants of these classes are not supported.</p>
*/
public class RemoteViews implements Parcelable, Filter {
diff --git a/android/widget/TextView.java b/android/widget/TextView.java
index d9bc51ff..71532a72 100644
--- a/android/widget/TextView.java
+++ b/android/widget/TextView.java
@@ -10836,6 +10836,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
int fromIndex, int removedCount, int addedCount) {
+ if (!AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)) {
+ return;
+ }
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
event.setFromIndex(fromIndex);
diff --git a/android/widget/Toast.java b/android/widget/Toast.java
index d8071200..bfde6ac3 100644
--- a/android/widget/Toast.java
+++ b/android/widget/Toast.java
@@ -504,7 +504,8 @@ public class Toast {
private void trySendAccessibilityEvent() {
AccessibilityManager accessibilityManager =
AccessibilityManager.getInstance(mView.getContext());
- if (!accessibilityManager.isEnabled()) {
+ if (!accessibilityManager.isObservedEventType(
+ AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)) {
return;
}
// treat toasts as notifications since they are used to
diff --git a/androidx/app/slice/ArrayUtils.java b/androidx/app/slice/ArrayUtils.java
new file mode 100644
index 00000000..669f66ae
--- /dev/null
+++ b/androidx/app/slice/ArrayUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.app.slice;
+
+
+import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
+
+import java.lang.reflect.Array;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+class ArrayUtils {
+
+ public static <T> boolean contains(T[] array, T item) {
+ for (T t : array) {
+ if (Objects.equals(t, item)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static <T> T[] appendElement(Class<T> kind, T[] array, T element) {
+ final T[] result;
+ final int end;
+ if (array != null) {
+ end = array.length;
+ result = (T[]) Array.newInstance(kind, end + 1);
+ System.arraycopy(array, 0, result, 0, end);
+ } else {
+ end = 0;
+ result = (T[]) Array.newInstance(kind, 1);
+ }
+ result[end] = element;
+ return result;
+ }
+
+ public static <T> T[] removeElement(Class<T> kind, T[] array, T element) {
+ if (array != null) {
+ if (!contains(array, element)) {
+ return array;
+ }
+ final int length = array.length;
+ for (int i = 0; i < length; i++) {
+ if (Objects.equals(array[i], element)) {
+ if (length == 1) {
+ return null;
+ }
+ T[] result = (T[]) Array.newInstance(kind, length - 1);
+ System.arraycopy(array, 0, result, 0, i);
+ System.arraycopy(array, i + 1, result, i, length - i - 1);
+ return result;
+ }
+ }
+ }
+ return array;
+ }
+}
diff --git a/androidx/app/slice/Slice.java b/androidx/app/slice/Slice.java
new file mode 100644
index 00000000..fb2395e1
--- /dev/null
+++ b/androidx/app/slice/Slice.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.app.slice;
+
+import static android.app.slice.Slice.HINT_ACTIONS;
+import static android.app.slice.Slice.HINT_HORIZONTAL;
+import static android.app.slice.Slice.HINT_LARGE;
+import static android.app.slice.Slice.HINT_LIST;
+import static android.app.slice.Slice.HINT_LIST_ITEM;
+import static android.app.slice.Slice.HINT_NO_TINT;
+import static android.app.slice.Slice.HINT_PARTIAL;
+import static android.app.slice.Slice.HINT_SELECTED;
+import static android.app.slice.Slice.HINT_TITLE;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_COLOR;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
+import android.support.annotation.StringDef;
+import android.support.v4.os.BuildCompat;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import androidx.app.slice.compat.SliceProviderCompat;
+import androidx.app.slice.core.SliceHints;
+import androidx.app.slice.core.SliceSpecs;
+
+/**
+ * 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.
+ */
+public final class Slice {
+
+ private static final String HINTS = "hints";
+ private static final String ITEMS = "items";
+ private static final String URI = "uri";
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
+ HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL,
+ SliceHints.HINT_HIDDEN, SliceHints.HINT_TOGGLE})
+ public @interface SliceHint{ }
+
+ private final SliceItem[] mItems;
+ private final @SliceHint String[] mHints;
+ private Uri mUri;
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
+ mHints = hints;
+ mItems = items.toArray(new SliceItem[items.size()]);
+ mUri = uri;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ public Slice(Bundle in) {
+ mHints = in.getStringArray(HINTS);
+ Parcelable[] items = in.getParcelableArray(ITEMS);
+ mItems = new SliceItem[items.length];
+ for (int i = 0; i < mItems.length; i++) {
+ if (items[i] instanceof Bundle) {
+ mItems[i] = new SliceItem((Bundle) items[i]);
+ }
+ }
+ mUri = in.getParcelable(URI);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ public Bundle toBundle() {
+ Bundle b = new Bundle();
+ b.putStringArray(HINTS, mHints);
+ Parcelable[] p = new Parcelable[mItems.length];
+ for (int i = 0; i < mItems.length; i++) {
+ p[i] = mItems[i].toBundle();
+ }
+ b.putParcelableArray(ITEMS, p);
+ b.putParcelable(URI, mUri);
+ return b;
+ }
+
+ /**
+ * @return The Uri that this Slice represents.
+ */
+ public Uri getUri() {
+ return mUri;
+ }
+
+ /**
+ * @return All child {@link SliceItem}s that this Slice contains.
+ */
+ public List<SliceItem> getItems() {
+ return Arrays.asList(mItems);
+ }
+
+ /**
+ * @return All hints associated with this Slice.
+ */
+ public @SliceHint List<String> getHints() {
+ return Arrays.asList(mHints);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public boolean hasHint(@SliceHint String hint) {
+ return ArrayUtils.contains(mHints, hint);
+ }
+
+ /**
+ * A Builder used to construct {@link Slice}s
+ */
+ public static class Builder {
+
+ private final Uri mUri;
+ private ArrayList<SliceItem> mItems = new ArrayList<>();
+ private @SliceHint ArrayList<String> mHints = new ArrayList<>();
+
+ /**
+ * Create a builder which will construct a {@link Slice} for the Given Uri.
+ * @param uri Uri to tag for this slice.
+ */
+ public Builder(@NonNull Uri uri) {
+ mUri = uri;
+ }
+
+ /**
+ * Create a builder for a {@link Slice} that is a sub-slice of the slice
+ * being constructed by the provided builder.
+ * @param parent The builder constructing the parent slice
+ */
+ public Builder(@NonNull Slice.Builder parent) {
+ mUri = parent.mUri.buildUpon().appendPath("_gen")
+ .appendPath(String.valueOf(mItems.size())).build();
+ }
+
+ /**
+ * Add hints to the Slice being constructed
+ */
+ public Builder addHints(@SliceHint String... hints) {
+ mHints.addAll(Arrays.asList(hints));
+ return this;
+ }
+
+ /**
+ * Add 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) {
+ return addSubSlice(slice, null);
+ }
+
+ /**
+ * Add a sub-slice to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Builder addSubSlice(@NonNull Slice slice, String subType) {
+ mItems.add(new SliceItem(slice, FORMAT_SLICE, subType, slice.getHints().toArray(
+ new String[slice.getHints().size()])));
+ return this;
+ }
+
+ /**
+ * Add an action to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Slice.Builder addAction(@NonNull PendingIntent action,
+ @NonNull Slice s, @Nullable String subType) {
+ mItems.add(new SliceItem(action, s, FORMAT_ACTION, subType, new String[0]));
+ return this;
+ }
+
+ /**
+ * Add text to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Builder addText(CharSequence text, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(text, FORMAT_TEXT, subType, hints));
+ return this;
+ }
+
+ /**
+ * Add text to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Builder addText(CharSequence text, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addText(text, subType, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
+ * Add an image to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Builder addIcon(Icon icon, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(icon, FORMAT_IMAGE, subType, hints));
+ return this;
+ }
+
+ /**
+ * Add an image to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Builder addIcon(Icon icon, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addIcon(icon, subType, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
+ * Add remote input to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
+ * Add remote input to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(remoteInput, FORMAT_REMOTE_INPUT, subType, hints));
+ return this;
+ }
+
+ /**
+ * Add a color to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Builder addColor(int color, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(color, FORMAT_COLOR, subType, hints));
+ return this;
+ }
+
+ /**
+ * Add a color to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Builder addColor(int color, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addColor(color, subType, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
+ * Add a timestamp to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Slice.Builder addTimestamp(long time, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(time, FORMAT_TIMESTAMP, subType, hints));
+ return this;
+ }
+
+ /**
+ * Add a timestamp to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Slice.Builder addTimestamp(long time, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addTimestamp(time, subType, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
+ * Construct the slice.
+ */
+ public Slice build() {
+ return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri);
+ }
+ }
+
+ /**
+ * @hide
+ * @return A string representation of this slice.
+ */
+ @RestrictTo(Scope.LIBRARY)
+ @Override
+ public String toString() {
+ return toString("");
+ }
+
+ private String toString(String indent) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mItems.length; i++) {
+ sb.append(indent);
+ if (FORMAT_SLICE.equals(mItems[i].getFormat())) {
+ sb.append("slice:\n");
+ sb.append(mItems[i].getSlice().toString(indent + " "));
+ } else if (FORMAT_TEXT.equals(mItems[i].getFormat())) {
+ sb.append("text: ");
+ sb.append(mItems[i].getText());
+ sb.append("\n");
+ } else {
+ sb.append(SliceItem.typeToString(mItems[i].getFormat()));
+ sb.append("\n");
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Turns a slice Uri into slice content.
+ *
+ * @param context Context 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
+ */
+ @SuppressWarnings("NewApi")
+ public static @Nullable Slice bindSlice(Context context, @NonNull Uri uri) {
+ if (BuildCompat.isAtLeastP()) {
+ return callBindSlice(context, uri);
+ } else {
+ return SliceProviderCompat.bindSlice(context, uri);
+ }
+ }
+
+ @TargetApi(28)
+ private static Slice callBindSlice(Context context, Uri uri) {
+ return SliceConvert.wrap(android.app.slice.Slice.bindSlice(
+ context.getContentResolver(), uri, SliceSpecs.SUPPORTED_SPECS));
+ }
+
+
+ /**
+ * Turns a slice intent into slice content. Expects an explicit intent. If there is no
+ * {@link ContentProvider} associated with the given intent this will throw
+ * {@link IllegalArgumentException}.
+ *
+ * @param context The context to use.
+ * @param intent The intent associated with a slice.
+ * @return The Slice provided by the app or null if none is given.
+ * @see Slice
+ * @see SliceProvider#onMapIntentToUri(Intent)
+ * @see Intent
+ */
+ @SuppressWarnings("NewApi")
+ public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) {
+ if (BuildCompat.isAtLeastP()) {
+ return callBindSlice(context, intent);
+ } else {
+ return SliceProviderCompat.bindSlice(context, intent);
+ }
+ }
+
+ @TargetApi(28)
+ private static Slice callBindSlice(Context context, Intent intent) {
+ return SliceConvert.wrap(android.app.slice.Slice.bindSlice(
+ context, intent, SliceSpecs.SUPPORTED_SPECS));
+ }
+}
diff --git a/androidx/app/slice/SliceConvert.java b/androidx/app/slice/SliceConvert.java
new file mode 100644
index 00000000..edbc293a
--- /dev/null
+++ b/androidx/app/slice/SliceConvert.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.app.slice;
+
+
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_COLOR;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+
+import android.support.annotation.RequiresApi;
+
+/**
+ * Convert between {@link androidx.app.slice.Slice} and {@link android.app.slice.Slice}
+ */
+@RequiresApi(28)
+public class SliceConvert {
+
+ /**
+ * Convert {@link androidx.app.slice.Slice} to {@link android.app.slice.Slice}
+ */
+ public static android.app.slice.Slice unwrap(androidx.app.slice.Slice slice) {
+ android.app.slice.Slice.Builder builder = new android.app.slice.Slice.Builder(
+ slice.getUri());
+ builder.addHints(slice.getHints());
+ for (androidx.app.slice.SliceItem item : slice.getItems()) {
+ switch (item.getFormat()) {
+ case FORMAT_SLICE:
+ builder.addSubSlice(unwrap(item.getSlice()), item.getSubType());
+ break;
+ case FORMAT_IMAGE:
+ builder.addIcon(item.getIcon(), item.getSubType(), item.getHints());
+ break;
+ case FORMAT_REMOTE_INPUT:
+ builder.addRemoteInput(item.getRemoteInput(), item.getSubType(),
+ item.getHints());
+ break;
+ case FORMAT_ACTION:
+ builder.addAction(item.getAction(), unwrap(item.getSlice()), item.getSubType());
+ break;
+ case FORMAT_TEXT:
+ builder.addText(item.getText(), item.getSubType(), item.getHints());
+ break;
+ case FORMAT_COLOR:
+ builder.addColor(item.getColor(), item.getSubType(), item.getHints());
+ break;
+ case FORMAT_TIMESTAMP:
+ builder.addTimestamp(item.getTimestamp(), item.getSubType(), item.getHints());
+ break;
+ }
+ }
+ return builder.build();
+ }
+
+ /**
+ * Convert {@link android.app.slice.Slice} to {@link androidx.app.slice.Slice}
+ */
+ public static androidx.app.slice.Slice wrap(android.app.slice.Slice slice) {
+ androidx.app.slice.Slice.Builder builder = new androidx.app.slice.Slice.Builder(
+ slice.getUri());
+ builder.addHints(slice.getHints());
+ for (android.app.slice.SliceItem item : slice.getItems()) {
+ switch (item.getFormat()) {
+ case FORMAT_SLICE:
+ builder.addSubSlice(wrap(item.getSlice()), item.getSubType());
+ break;
+ case FORMAT_IMAGE:
+ builder.addIcon(item.getIcon(), item.getSubType(), item.getHints());
+ break;
+ case FORMAT_REMOTE_INPUT:
+ builder.addRemoteInput(item.getRemoteInput(), item.getSubType(),
+ item.getHints());
+ break;
+ case FORMAT_ACTION:
+ builder.addAction(item.getAction(), wrap(item.getSlice()), item.getSubType());
+ break;
+ case FORMAT_TEXT:
+ builder.addText(item.getText(), item.getSubType(), item.getHints());
+ break;
+ case FORMAT_COLOR:
+ builder.addColor(item.getColor(), item.getSubType(), item.getHints());
+ break;
+ case FORMAT_TIMESTAMP:
+ builder.addTimestamp(item.getTimestamp(), item.getSubType(), item.getHints());
+ break;
+ }
+ }
+ return builder.build();
+ }
+}
diff --git a/androidx/app/slice/SliceItem.java b/androidx/app/slice/SliceItem.java
new file mode 100644
index 00000000..e4412d1f
--- /dev/null
+++ b/androidx/app/slice/SliceItem.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 androidx.app.slice;
+
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_COLOR;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
+import android.support.annotation.StringDef;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ * A SliceItem is a single unit in the tree structure of a {@link Slice}.
+ * <p>
+ * A SliceItem a piece of content and some hints about what that content
+ * means or how it should be displayed. The types of content can be:
+ * <li>{@link android.app.slice.SliceItem#FORMAT_SLICE}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_TEXT}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_IMAGE}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_ACTION}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_COLOR}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_TIMESTAMP}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_REMOTE_INPUT}</li>
+ * <p>
+ * 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}.
+ */
+public class SliceItem {
+
+ private static final String HINTS = "hints";
+ private static final String FORMAT = "format";
+ private static final String SUBTYPE = "subtype";
+ private static final String OBJ = "obj";
+ private static final String OBJ_2 = "obj_2";
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ @StringDef({FORMAT_SLICE, FORMAT_TEXT, FORMAT_IMAGE, FORMAT_ACTION, FORMAT_COLOR,
+ FORMAT_TIMESTAMP, FORMAT_REMOTE_INPUT})
+ public @interface SliceType {
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ protected @Slice.SliceHint String[] mHints;
+ private final String mFormat;
+ private final String mSubType;
+ private final Object mObj;
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ public SliceItem(Object obj, @SliceType String format, String subType,
+ @Slice.SliceHint String[] hints) {
+ mHints = hints;
+ mFormat = format;
+ mSubType = subType;
+ mObj = obj;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ public SliceItem(PendingIntent intent, Slice slice, String format, String subType,
+ @Slice.SliceHint String[] hints) {
+ this(new Pair<>(intent, slice), format, subType, hints);
+ }
+
+ /**
+ * Gets all hints associated with this SliceItem.
+ *
+ * @return Array of hints.
+ */
+ public @NonNull @Slice.SliceHint List<String> getHints() {
+ return Arrays.asList(mHints);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ public void addHint(@Slice.SliceHint String hint) {
+ mHints = ArrayUtils.appendElement(String.class, mHints, hint);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ public void removeHint(String hint) {
+ ArrayUtils.removeElement(String.class, mHints, hint);
+ }
+
+ /**
+ * Get the format of this SliceItem.
+ * <p>
+ * The format will be one of the following types supported by the platform:
+ * <li>{@link android.app.slice.SliceItem#FORMAT_SLICE}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_TEXT}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_IMAGE}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_ACTION}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_COLOR}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_TIMESTAMP}</li>
+ * <li>{@link android.app.slice.SliceItem#FORMAT_REMOTE_INPUT}</li>
+ * @see #getSubType() ()
+ */
+ public @SliceType String getFormat() {
+ return mFormat;
+ }
+
+ /**
+ * Get the sub-type of this SliceItem.
+ * <p>
+ * Subtypes provide additional information about the type of this information beyond basic
+ * interpretations inferred by {@link #getFormat()}. For example a slice may contain
+ * many {@link android.app.slice.SliceItem#FORMAT_TEXT} items, but only some of them may be
+ * {@link android.app.slice.Slice#SUBTYPE_MESSAGE}.
+ * @see #getFormat()
+ */
+ public String getSubType() {
+ return mSubType;
+ }
+
+ /**
+ * @return The text held by this {@link android.app.slice.SliceItem#FORMAT_TEXT} SliceItem
+ */
+ public CharSequence getText() {
+ return (CharSequence) mObj;
+ }
+
+ /**
+ * @return The icon held by this {@link android.app.slice.SliceItem#FORMAT_IMAGE} SliceItem
+ */
+ @RequiresApi(23)
+ public Icon getIcon() {
+ return (Icon) mObj;
+ }
+
+ /**
+ * @return The pending intent held by this {@link android.app.slice.SliceItem#FORMAT_ACTION}
+ * SliceItem
+ */
+ public PendingIntent getAction() {
+ return ((Pair<PendingIntent, Slice>) mObj).first;
+ }
+
+ /**
+ * @return The remote input held by this {@link android.app.slice.SliceItem#FORMAT_REMOTE_INPUT}
+ * SliceItem
+ */
+ @RequiresApi(20)
+ public RemoteInput getRemoteInput() {
+ return (RemoteInput) mObj;
+ }
+
+ /**
+ * @return The color held by this {@link android.app.slice.SliceItem#FORMAT_COLOR} SliceItem
+ */
+ public int getColor() {
+ return (Integer) mObj;
+ }
+
+ /**
+ * @return The slice held by this {@link android.app.slice.SliceItem#FORMAT_ACTION} or
+ * {@link android.app.slice.SliceItem#FORMAT_SLICE} SliceItem
+ */
+ public Slice getSlice() {
+ if (FORMAT_ACTION.equals(getFormat())) {
+ return ((Pair<PendingIntent, Slice>) mObj).second;
+ }
+ return (Slice) mObj;
+ }
+
+ /**
+ * @return The timestamp held by this {@link android.app.slice.SliceItem#FORMAT_TIMESTAMP}
+ * SliceItem
+ */
+ public long getTimestamp() {
+ return (Long) mObj;
+ }
+
+ /**
+ * @param hint The hint to check for
+ * @return true if this item contains the given hint
+ */
+ public boolean hasHint(@Slice.SliceHint String hint) {
+ return ArrayUtils.contains(mHints, hint);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ public SliceItem(Bundle in) {
+ mHints = in.getStringArray(HINTS);
+ mFormat = in.getString(FORMAT);
+ mSubType = in.getString(SUBTYPE);
+ mObj = readObj(mFormat, in);
+ }
+
+ /**
+ * @hide
+ * @return
+ */
+ @RestrictTo(Scope.LIBRARY)
+ public Bundle toBundle() {
+ Bundle b = new Bundle();
+ b.putStringArray(HINTS, mHints);
+ b.putString(FORMAT, mFormat);
+ b.putString(SUBTYPE, mSubType);
+ writeObj(b, mObj, mFormat);
+ return b;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ public boolean hasHints(@Slice.SliceHint String[] hints) {
+ if (hints == null) return true;
+ for (String hint : hints) {
+ if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ public boolean hasAnyHints(@Slice.SliceHint String[] hints) {
+ if (hints == null) return false;
+ for (String hint : hints) {
+ if (ArrayUtils.contains(mHints, hint)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void writeObj(Bundle dest, Object obj, String type) {
+ switch (type) {
+ case FORMAT_IMAGE:
+ case FORMAT_REMOTE_INPUT:
+ dest.putParcelable(OBJ, (Parcelable) obj);
+ break;
+ case FORMAT_SLICE:
+ dest.putParcelable(OBJ, ((Slice) obj).toBundle());
+ break;
+ case FORMAT_ACTION:
+ dest.putParcelable(OBJ, ((Pair<PendingIntent, Slice>) obj).first);
+ dest.putBundle(OBJ_2, ((Pair<PendingIntent, Slice>) obj).second.toBundle());
+ break;
+ case FORMAT_TEXT:
+ dest.putCharSequence(OBJ, (CharSequence) obj);
+ break;
+ case FORMAT_COLOR:
+ dest.putInt(OBJ, (Integer) mObj);
+ break;
+ case FORMAT_TIMESTAMP:
+ dest.putLong(OBJ, (Long) mObj);
+ break;
+ }
+ }
+
+ private static Object readObj(String type, Bundle in) {
+ switch (type) {
+ case FORMAT_IMAGE:
+ case FORMAT_REMOTE_INPUT:
+ return in.getParcelable(OBJ);
+ case FORMAT_SLICE:
+ return new Slice(in.getBundle(OBJ));
+ case FORMAT_TEXT:
+ return in.getCharSequence(OBJ);
+ case FORMAT_ACTION:
+ return new Pair<>(
+ (PendingIntent) in.getParcelable(OBJ),
+ new Slice(in.getBundle(OBJ_2)));
+ case FORMAT_COLOR:
+ return in.getInt(OBJ);
+ case FORMAT_TIMESTAMP:
+ return in.getLong(OBJ);
+ }
+ throw new RuntimeException("Unsupported type " + type);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY)
+ public static String typeToString(String format) {
+ switch (format) {
+ case FORMAT_SLICE:
+ return "Slice";
+ case FORMAT_TEXT:
+ return "Text";
+ case FORMAT_IMAGE:
+ return "Image";
+ case FORMAT_ACTION:
+ return "Action";
+ case FORMAT_COLOR:
+ return "Color";
+ case FORMAT_TIMESTAMP:
+ return "Timestamp";
+ case FORMAT_REMOTE_INPUT:
+ return "RemoteInput";
+ }
+ return "Unrecognized format: " + format;
+ }
+}
diff --git a/androidx/app/slice/SliceProvider.java b/androidx/app/slice/SliceProvider.java
new file mode 100644
index 00000000..a0c12f12
--- /dev/null
+++ b/androidx/app/slice/SliceProvider.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.app.slice;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ProviderInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.v4.os.BuildCompat;
+
+import androidx.app.slice.compat.ContentProviderWrapper;
+import androidx.app.slice.compat.SliceProviderCompat;
+import androidx.app.slice.compat.SliceProviderWrapper;
+
+/**
+ * A SliceProvider allows an app to provide content to be displayed in system spaces. This content
+ * is templated and can contain actions, and the behavior of how it is surfaced is specific to the
+ * system surface.
+ * <p>
+ * Slices are not currently live content. They are bound once and shown to the user. If the content
+ * changes due to a callback from user interaction, then
+ * {@link ContentResolver#notifyChange(Uri, ContentObserver)} should be used to notify the system.
+ * </p>
+ * <p>
+ * The provider needs to be declared in the manifest to provide the authority for the app. The
+ * authority for most slices is expected to match the package of the application.
+ * </p>
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <provider
+ * android:name="com.android.mypkg.MySliceProvider"
+ * android:authorities="com.android.mypkg" />}
+ * </pre>
+ * <p>
+ * Slices can be identified by a Uri or by an Intent. To link an Intent with a slice, the provider
+ * must have an {@link IntentFilter} matching the slice intent. When a slice is being requested via
+ * an intent, {@link #onMapIntentToUri(Intent)} can be called and is expected to return an
+ * appropriate Uri representing the slice.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <provider
+ * android:name="com.android.mypkg.MySliceProvider"
+ * android:authorities="com.android.mypkg">
+ * <intent-filter>
+ * <action android:name="android.intent.action.MY_SLICE_INTENT" />
+ * </intent-filter>
+ * </provider>}
+ * </pre>
+ *
+ * @see android.app.slice.Slice
+ */
+public abstract class SliceProvider extends ContentProviderWrapper {
+
+ @Override
+ public void attachInfo(Context context, ProviderInfo info) {
+ ContentProvider impl;
+ if (BuildCompat.isAtLeastP()) {
+ impl = new SliceProviderWrapper(this);
+ } else {
+ impl = new SliceProviderCompat(this);
+ }
+ super.attachInfo(context, info, impl);
+ }
+
+ /**
+ * Implement this to initialize your slice provider on startup.
+ * This method is called for all registered slice providers on the
+ * application main thread at application launch time. It must not perform
+ * lengthy operations, or application startup will be delayed.
+ *
+ * <p>You should defer nontrivial initialization (such as opening,
+ * upgrading, and scanning databases) until the slice provider is used
+ * (via #onBindSlice, etc). Deferred initialization
+ * keeps application startup fast, avoids unnecessary work if the provider
+ * turns out not to be needed, and stops database errors (such as a full
+ * disk) from halting application launch.
+ *
+ * @return true if the provider was successfully loaded, false otherwise
+ */
+ public abstract boolean onCreateSliceProvider();
+
+ /**
+ * 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 android.app.slice.Slice#HINT_PARTIAL}
+ */
+ // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
+ public abstract Slice onBindSlice(Uri sliceUri);
+
+ /**
+ * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider.
+ * In that case, this method can be called and is expected to return a non-null Uri representing
+ * a slice. Otherwise this will throw {@link UnsupportedOperationException}.
+ *
+ * @return Uri representing the slice associated with the provided intent.
+ * @see {@link android.app.slice.Slice}
+ */
+ public @NonNull Uri onMapIntentToUri(Intent intent) {
+ throw new UnsupportedOperationException(
+ "This provider has not implemented intent to uri mapping");
+ }
+}
diff --git a/androidx/app/slice/builders/MessagingSliceBuilder.java b/androidx/app/slice/builders/MessagingSliceBuilder.java
index 29189048..424c8efb 100644
--- a/androidx/app/slice/builders/MessagingSliceBuilder.java
+++ b/androidx/app/slice/builders/MessagingSliceBuilder.java
@@ -18,12 +18,13 @@ package androidx.app.slice.builders;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import android.app.slice.Slice;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
+import androidx.app.slice.Slice;
+
/**
* Builder to construct slice content in a messaging format.
*/
@@ -41,7 +42,7 @@ public class MessagingSliceBuilder extends TemplateSliceBuilder {
/**
* Create a {@link MessageBuilder} that will be added to this slice when
- * {@link MessageBuilder#finish()} is called.
+ * {@link MessageBuilder#endMessage()} is called.
* @return a new message builder
*/
public MessageBuilder startMessage() {
@@ -58,7 +59,7 @@ public class MessagingSliceBuilder extends TemplateSliceBuilder {
@Override
public void add(SubTemplateSliceBuilder builder) {
- getBuilder().addSubSlice(builder.build());
+ getBuilder().addSubSlice(builder.build(), android.app.slice.Slice.SUBTYPE_MESSAGE);
}
/**
@@ -72,14 +73,13 @@ public class MessagingSliceBuilder extends TemplateSliceBuilder {
@RestrictTo(RestrictTo.Scope.LIBRARY)
public MessageBuilder(MessagingSliceBuilder parent) {
super(parent.createChildBuilder(), parent);
- getBuilder().addHints(Slice.HINT_MESSAGE);
}
/**
* Add the icon used to display contact in the messaging experience
*/
public MessageBuilder addSource(Icon source) {
- getBuilder().addIcon(source, Slice.HINT_SOURCE);
+ getBuilder().addIcon(source, android.app.slice.Slice.SUBTYPE_SOURCE);
return this;
}
@@ -87,7 +87,7 @@ public class MessagingSliceBuilder extends TemplateSliceBuilder {
* Add the text to be used for this message.
*/
public MessageBuilder addText(CharSequence text) {
- getBuilder().addText(text);
+ getBuilder().addText(text, null);
return this;
}
@@ -95,7 +95,7 @@ public class MessagingSliceBuilder extends TemplateSliceBuilder {
* Add the time at which this message arrived in ms since Unix epoch
*/
public MessageBuilder addTimestamp(long timestamp) {
- getBuilder().addTimestamp(timestamp);
+ getBuilder().addTimestamp(timestamp, null);
return this;
}
diff --git a/androidx/app/slice/builders/TemplateSliceBuilder.java b/androidx/app/slice/builders/TemplateSliceBuilder.java
index 61fea7f9..102ee001 100644
--- a/androidx/app/slice/builders/TemplateSliceBuilder.java
+++ b/androidx/app/slice/builders/TemplateSliceBuilder.java
@@ -17,10 +17,11 @@
package androidx.app.slice.builders;
-import android.app.slice.Slice;
import android.net.Uri;
import android.support.annotation.RestrictTo;
+import androidx.app.slice.Slice;
+
/**
* Base class of builders of various template types.
*/
diff --git a/androidx/app/slice/compat/ContentProviderWrapper.java b/androidx/app/slice/compat/ContentProviderWrapper.java
new file mode 100644
index 00000000..944a5ac0
--- /dev/null
+++ b/androidx/app/slice/compat/ContentProviderWrapper.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.app.slice.compat;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
+
+/**
+ * @hide
+ */
+// TODO: Remove as soon as we have better systems in place for this.
+@RestrictTo(Scope.LIBRARY)
+public class ContentProviderWrapper extends ContentProvider {
+
+ private ContentProvider mImpl;
+
+ /**
+ * Triggers an attach with the object to wrap.
+ */
+ public void attachInfo(Context context, ProviderInfo info, ContentProvider impl) {
+ mImpl = impl;
+ mImpl.attachInfo(context, info);
+ super.attachInfo(context, info);
+ }
+
+ @Override
+ public final boolean onCreate() {
+ return mImpl.onCreate();
+ }
+
+ @Nullable
+ @Override
+ public final Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ @Nullable String selection, @Nullable String[] selectionArgs,
+ @Nullable String sortOrder) {
+ return mImpl.query(uri, projection, selection, selectionArgs, sortOrder);
+ }
+
+ @Nullable
+ @Override
+ @RequiresApi(28)
+ public final Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) {
+ return mImpl.query(uri, projection, queryArgs, cancellationSignal);
+ }
+
+ @Nullable
+ @Override
+ public final Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ @Nullable String selection, @Nullable String[] selectionArgs,
+ @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
+ return mImpl.query(uri, projection, selection, selectionArgs, sortOrder,
+ cancellationSignal);
+ }
+
+ @Nullable
+ @Override
+ public final String getType(@NonNull Uri uri) {
+ return mImpl.getType(uri);
+ }
+
+ @Nullable
+ @Override
+ public final Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+ return mImpl.insert(uri, values);
+ }
+
+ @Override
+ public final int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
+ return mImpl.bulkInsert(uri, values);
+ }
+
+ @Override
+ public final int delete(@NonNull Uri uri, @Nullable String selection,
+ @Nullable String[] selectionArgs) {
+ return mImpl.delete(uri, selection, selectionArgs);
+ }
+
+ @Override
+ public final int update(@NonNull Uri uri, @Nullable ContentValues values,
+ @Nullable String selection, @Nullable String[] selectionArgs) {
+ return mImpl.update(uri, values, selection, selectionArgs);
+ }
+
+ @Nullable
+ @Override
+ public final Bundle call(@NonNull String method, @Nullable String arg,
+ @Nullable Bundle extras) {
+ return mImpl.call(method, arg, extras);
+ }
+
+ @Nullable
+ @Override
+ public final Uri canonicalize(@NonNull Uri url) {
+ return mImpl.canonicalize(url);
+ }
+}
diff --git a/androidx/app/slice/compat/SliceProviderCompat.java b/androidx/app/slice/compat/SliceProviderCompat.java
new file mode 100644
index 00000000..9fcac1b5
--- /dev/null
+++ b/androidx/app/slice/compat/SliceProviderCompat.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.app.slice.compat;
+
+import static android.app.slice.SliceProvider.SLICE_TYPE;
+
+import android.Manifest.permission;
+import android.content.ContentProvider;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceProvider;
+
+/**
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public class SliceProviderCompat extends ContentProvider {
+
+ private static final String TAG = "SliceProvider";
+
+ public static final String EXTRA_BIND_URI = "slice_uri";
+ public static final String METHOD_SLICE = "bind_slice";
+ public static final String METHOD_MAP_INTENT = "map_slice";
+ public static final String EXTRA_INTENT = "slice_intent";
+ public static final String EXTRA_SLICE = "slice";
+
+ private static final boolean DEBUG = false;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private SliceProvider mSliceProvider;
+
+ public SliceProviderCompat(SliceProvider provider) {
+ mSliceProvider = provider;
+ }
+
+ @Override
+ public boolean onCreate() {
+ return mSliceProvider.onCreateSliceProvider();
+ }
+
+ @Override
+ public final int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ if (DEBUG) Log.d(TAG, "update " + uri);
+ return 0;
+ }
+
+ @Override
+ public final int delete(Uri uri, String selection, String[] selectionArgs) {
+ if (DEBUG) Log.d(TAG, "delete " + uri);
+ return 0;
+ }
+
+ @Override
+ public final Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ if (DEBUG) Log.d(TAG, "query " + uri);
+ return null;
+ }
+
+ @Override
+ public final Cursor query(Uri uri, String[] projection, String selection, String[]
+ selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
+ if (DEBUG) Log.d(TAG, "query " + uri);
+ return null;
+ }
+
+ @Override
+ public final Cursor query(Uri uri, String[] projection, Bundle queryArgs,
+ CancellationSignal cancellationSignal) {
+ if (DEBUG) Log.d(TAG, "query " + uri);
+ return null;
+ }
+
+ @Override
+ public final Uri insert(Uri uri, ContentValues values) {
+ if (DEBUG) Log.d(TAG, "insert " + uri);
+ return null;
+ }
+
+ @Override
+ public final String getType(Uri uri) {
+ if (DEBUG) Log.d(TAG, "getFormat " + uri);
+ return SLICE_TYPE;
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ if (method.equals(METHOD_SLICE)) {
+ Uri uri = extras.getParcelable(EXTRA_BIND_URI);
+ if (Binder.getCallingUid() != Process.myUid()) {
+ getContext().enforceUriPermission(uri, permission.BIND_SLICE,
+ permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ "Slice binding requires the permission BIND_SLICE");
+ }
+
+ Slice s = handleBindSlice(uri);
+ Bundle b = new Bundle();
+ b.putParcelable(EXTRA_SLICE, s.toBundle());
+ return b;
+ } else if (method.equals(METHOD_MAP_INTENT)) {
+ if (Binder.getCallingUid() != Process.myUid()) {
+ getContext().enforceCallingPermission(permission.BIND_SLICE,
+ "Slice binding requires the permission BIND_SLICE");
+ }
+ Intent intent = extras.getParcelable(EXTRA_INTENT);
+ Uri uri = mSliceProvider.onMapIntentToUri(intent);
+ Bundle b = new Bundle();
+ if (uri != null) {
+ Slice s = handleBindSlice(uri);
+ b.putParcelable(EXTRA_SLICE, s.toBundle());
+ } else {
+ b.putParcelable(EXTRA_SLICE, null);
+ }
+ return b;
+ }
+ return super.call(method, arg, extras);
+ }
+
+ private Slice handleBindSlice(final Uri sliceUri) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ return onBindSliceStrict(sliceUri);
+ } else {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Slice[] output = new Slice[1];
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ output[0] = onBindSliceStrict(sliceUri);
+ latch.countDown();
+ }
+ });
+ try {
+ latch.await();
+ return output[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private Slice onBindSliceStrict(Uri sliceUri) {
+ ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ try {
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyDeath()
+ .build());
+ return mSliceProvider.onBindSlice(sliceUri);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ /**
+ * Compat version of {@link Slice#bindSlice(Context, Uri)}.
+ */
+ public static Slice bindSlice(Context context, Uri uri) {
+ ContentProviderClient provider = context.getContentResolver()
+ .acquireContentProviderClient(uri);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ try {
+ Bundle extras = new Bundle();
+ extras.putParcelable(EXTRA_BIND_URI, uri);
+ final Bundle res = provider.call(METHOD_SLICE, null, extras);
+ if (res == null) {
+ return null;
+ }
+ Parcelable bundle = res.getParcelable(EXTRA_SLICE);
+ if (!(bundle instanceof Bundle)) {
+ return null;
+ }
+ return new Slice((Bundle) bundle);
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ provider.close();
+ }
+ }
+
+ /**
+ * Compat version of {@link Slice#bindSlice(Context, Intent)}.
+ */
+ public static Slice bindSlice(Context context, Intent intent) {
+ ContentResolver resolver = context.getContentResolver();
+
+ // Check if the intent has data for the slice uri on it and use that
+ final Uri intentData = intent.getData();
+ if (intentData != null && SLICE_TYPE.equals(resolver.getType(intentData))) {
+ return bindSlice(context, intentData);
+ }
+ // Otherwise ask the app
+ List<ResolveInfo> providers =
+ context.getPackageManager().queryIntentContentProviders(intent, 0);
+ if (providers == null) {
+ throw new IllegalArgumentException("Unable to resolve intent " + intent);
+ }
+ String authority = providers.get(0).providerInfo.authority;
+ Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).build();
+ ContentProviderClient provider = resolver.acquireContentProviderClient(uri);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ try {
+ Bundle extras = new Bundle();
+ extras.putParcelable(EXTRA_INTENT, intent);
+ final Bundle res = provider.call(METHOD_MAP_INTENT, null, extras);
+ if (res == null) {
+ return null;
+ }
+ Parcelable bundle = res.getParcelable(EXTRA_SLICE);
+ if (!(bundle instanceof Bundle)) {
+ return null;
+ }
+ return new Slice((Bundle) bundle);
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ provider.close();
+ }
+ }
+}
diff --git a/androidx/app/slice/compat/SliceProviderWrapper.java b/androidx/app/slice/compat/SliceProviderWrapper.java
new file mode 100644
index 00000000..3afed2b0
--- /dev/null
+++ b/androidx/app/slice/compat/SliceProviderWrapper.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.app.slice.compat;
+
+import android.annotation.TargetApi;
+import android.app.slice.Slice;
+import android.app.slice.SliceProvider;
+import android.app.slice.SliceSpec;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
+
+import java.util.List;
+
+import androidx.app.slice.SliceConvert;
+
+/**
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+@TargetApi(28)
+public class SliceProviderWrapper extends SliceProvider {
+
+ private androidx.app.slice.SliceProvider mSliceProvider;
+
+ public SliceProviderWrapper(androidx.app.slice.SliceProvider provider) {
+ mSliceProvider = provider;
+ }
+
+ @Override
+ public boolean onCreate() {
+ return mSliceProvider.onCreateSliceProvider();
+ }
+
+ @Override
+ public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedVersions) {
+ return SliceConvert.unwrap(mSliceProvider.onBindSlice(sliceUri));
+ }
+
+ /**
+ * Maps intents to uris.
+ */
+ @Override
+ public @NonNull Uri onMapIntentToUri(Intent intent) {
+ return mSliceProvider.onMapIntentToUri(intent);
+ }
+}
diff --git a/androidx/app/slice/builders/SliceHints.java b/androidx/app/slice/core/SliceHints.java
index 5db12193..6ebcd03a 100644
--- a/androidx/app/slice/builders/SliceHints.java
+++ b/androidx/app/slice/core/SliceHints.java
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package androidx.app.slice.builders;
+package androidx.app.slice.core;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.app.slice.Slice;
-import android.app.slice.widget.SliceView;
import android.support.annotation.RestrictTo;
-
/**
* Temporary class to contain hint constants for slices to be used.
* @hide
@@ -42,9 +40,11 @@ public class SliceHints {
*/
public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
/**
- * Hint to indicate that this content should not be shown in the {@link SliceView#MODE_SMALL}
- * and {@link SliceView#MODE_LARGE} modes of SliceView. This content may be used to populate
- * the {@link SliceView#MODE_SHORTCUT} format of the slice.
+ * Hint to indicate that this content should not be shown in the
+ * {@link androidx.app.slice.widget.SliceView#MODE_SMALL}
+ * and {@link androidx.app.slice.widget.SliceView#MODE_LARGE} modes of SliceView.
+ * This content may be used to populate
+ * the {@link androidx.app.slice.widget.SliceView#MODE_SHORTCUT} format of the slice.
*/
public static final String HINT_HIDDEN = "hidden";
}
diff --git a/androidx/app/slice/core/SliceQuery.java b/androidx/app/slice/core/SliceQuery.java
index e1430d05..9da5478f 100644
--- a/androidx/app/slice/core/SliceQuery.java
+++ b/androidx/app/slice/core/SliceQuery.java
@@ -16,8 +16,16 @@
package androidx.app.slice.core;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
+import static android.app.slice.Slice.HINT_ACTIONS;
+import static android.app.slice.Slice.HINT_LIST;
+import static android.app.slice.Slice.HINT_LIST_ITEM;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_COLOR;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
+import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+
+import android.annotation.TargetApi;
import android.support.annotation.RestrictTo;
import android.text.TextUtils;
@@ -26,17 +34,21 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Spliterators;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import androidx.app.slice.builders.SliceHints;
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
/**
* Utilities for finding content within a Slice.
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+// TODO: Not expect 24.
+@TargetApi(24)
public class SliceQuery {
/**
@@ -44,23 +56,23 @@ public class SliceQuery {
* front slot of a small slice.
*/
public static boolean isStartType(SliceItem item) {
- final int type = item.getType();
- return !item.hasHint(SliceHints.HINT_TOGGLE)
- && ((type == SliceItem.TYPE_ACTION && (find(item, SliceItem.TYPE_IMAGE) != null))
- || type == SliceItem.TYPE_IMAGE
- || type == SliceItem.TYPE_TIMESTAMP);
+ final String type = item.getFormat();
+ return (!item.hasHint(SliceHints.HINT_TOGGLE)
+ && (FORMAT_ACTION.equals(type) && (find(item, FORMAT_IMAGE) != null)))
+ || FORMAT_IMAGE.equals(type)
+ || FORMAT_TIMESTAMP.equals(type);
}
/**
* @return Finds the first slice that has non-slice children.
*/
public static SliceItem findFirstSlice(SliceItem slice) {
- if (slice.getType() != SliceItem.TYPE_SLICE) {
+ if (!FORMAT_SLICE.equals(slice.getFormat())) {
return slice;
}
List<SliceItem> items = slice.getSlice().getItems();
for (int i = 0; i < items.size(); i++) {
- if (items.get(i).getType() == SliceItem.TYPE_SLICE) {
+ if (FORMAT_SLICE.equals(items.get(i).getFormat())) {
SliceItem childSlice = items.get(i);
return findFirstSlice(childSlice);
} else {
@@ -76,14 +88,14 @@ public class SliceQuery {
* @return Whether this item is a simple action, i.e. an action that only has an icon.
*/
public static boolean isSimpleAction(SliceItem item) {
- if (item.getType() == SliceItem.TYPE_ACTION) {
+ if (FORMAT_ACTION.equals(item.getFormat())) {
List<SliceItem> items = item.getSlice().getItems();
boolean hasImage = false;
for (int i = 0; i < items.size(); i++) {
SliceItem child = items.get(i);
- if (child.getType() == SliceItem.TYPE_IMAGE && !hasImage) {
+ if (FORMAT_IMAGE.equals(child.getFormat()) && !hasImage) {
hasImage = true;
- } else if (child.getType() == SliceItem.TYPE_COLOR) {
+ } else if (FORMAT_COLOR.equals(child.getFormat())) {
continue;
} else {
return false;
@@ -136,14 +148,14 @@ public class SliceQuery {
*/
public static SliceItem getPrimaryIcon(Slice slice) {
for (SliceItem item : slice.getItems()) {
- if (item.getType() == SliceItem.TYPE_IMAGE) {
+ if (FORMAT_IMAGE.equals(item.getFormat())) {
return item;
}
- if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
- && !item.hasHint(Slice.HINT_ACTIONS)
- && !item.hasHint(Slice.HINT_LIST_ITEM)
- && (item.getType() != SliceItem.TYPE_ACTION)) {
- SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
+ if (!(FORMAT_SLICE.equals(item.getFormat()) && item.hasHint(HINT_LIST))
+ && !item.hasHint(HINT_ACTIONS)
+ && !item.hasHint(HINT_LIST_ITEM)
+ && !FORMAT_ACTION.equals(item.getFormat())) {
+ SliceItem icon = SliceQuery.find(item, FORMAT_IMAGE);
if (icon != null) {
return icon;
}
@@ -167,86 +179,128 @@ public class SliceQuery {
/**
*/
- private static boolean contains(SliceItem container, SliceItem item) {
+ private static boolean contains(SliceItem container, final SliceItem item) {
if (container == null || item == null) return false;
- return stream(container).filter(s -> (s == item)).findAny().isPresent();
+ return stream(container).filter(new Predicate<SliceItem>() {
+ @Override
+ public boolean test(SliceItem s) {
+ return s == item;
+ }
+ }).findAny().isPresent();
}
/**
*/
- public static List<SliceItem> findAll(SliceItem s, int type) {
- return findAll(s, type, (String[]) null, null);
+ public static List<SliceItem> findAll(SliceItem s, String format) {
+ return findAll(s, format, (String[]) null, null);
}
/**
*/
- public static List<SliceItem> findAll(Slice s, int type, String hints, String nonHints) {
- return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
+ public static List<SliceItem> findAll(Slice s, String format, String hints, String nonHints) {
+ return findAll(s, format, new String[]{ hints }, new String[]{ nonHints });
}
/**
*/
- public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) {
- return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
+ public static List<SliceItem> findAll(SliceItem s, String format, String hints,
+ String nonHints) {
+ return findAll(s, format, new String[]{ hints }, new String[]{ nonHints });
}
/**
*/
- public static List<SliceItem> findAll(Slice s, int type, String[] hints,
- String[] nonHints) {
- return stream(s).filter(item -> (type == -1 || item.getType() == type)
- && (hasHints(item, hints) && !hasAnyHints(item, nonHints)))
- .collect(Collectors.toList());
+ public static List<SliceItem> findAll(Slice s, final String format, final String[] hints,
+ final String[] nonHints) {
+ return stream(s).filter(new Predicate<SliceItem>() {
+ @Override
+ public boolean test(SliceItem item) {
+ return checkFormat(item, format)
+ && (hasHints(item, hints) && !hasAnyHints(item, nonHints));
+ }
+ }).collect(Collectors.<SliceItem>toList());
}
/**
*/
- public static List<SliceItem> findAll(SliceItem s, int type, String[] hints,
- String[] nonHints) {
- return stream(s).filter(item -> (type == -1 || item.getType() == type)
- && (hasHints(item, hints) && !hasAnyHints(item, nonHints)))
- .collect(Collectors.toList());
+ public static List<SliceItem> findAll(SliceItem s, final String format, final String[] hints,
+ final String[] nonHints) {
+ return stream(s).filter(new Predicate<SliceItem>() {
+ @Override
+ public boolean test(SliceItem item) {
+ return checkFormat(item, format)
+ && (hasHints(item, hints) && !hasAnyHints(item, nonHints));
+ }
+ }).collect(Collectors.<SliceItem>toList());
}
/**
*/
- public static SliceItem find(Slice s, int type, String hints, String nonHints) {
- return find(s, type, new String[]{ hints }, new String[]{ nonHints });
+ public static SliceItem find(Slice s, String format, String hints, String nonHints) {
+ return find(s, format, new String[]{ hints }, new String[]{ nonHints });
}
/**
*/
- public static SliceItem find(Slice s, int type) {
- return find(s, type, (String[]) null, null);
+ public static SliceItem find(Slice s, String format) {
+ return find(s, format, (String[]) null, null);
}
/**
*/
- public static SliceItem find(SliceItem s, int type) {
- return find(s, type, (String[]) null, null);
+ public static SliceItem find(SliceItem s, String format) {
+ return find(s, format, (String[]) null, null);
}
/**
*/
- public static SliceItem find(SliceItem s, int type, String hints, String nonHints) {
- return find(s, type, new String[]{ hints }, new String[]{ nonHints });
+ public static SliceItem find(SliceItem s, String format, String hints, String nonHints) {
+ return find(s, format, new String[]{ hints }, new String[]{ nonHints });
}
/**
*/
- public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
- List<String> h = s.getHints();
- return stream(s).filter(item -> (item.getType() == type || type == -1)
- && (hasHints(item, hints) && !hasAnyHints(item, nonHints))).findFirst()
- .orElse(null);
+ public static SliceItem find(Slice s, final String format, final String[] hints,
+ final String[] nonHints) {
+ return stream(s).filter(new Predicate<SliceItem>() {
+ @Override
+ public boolean test(SliceItem item) {
+ return checkFormat(item, format)
+ && (hasHints(item, hints) && !hasAnyHints(item, nonHints));
+ }
+ }).findFirst().orElse(null);
}
/**
*/
- public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) {
- return stream(s).filter(item -> (item.getType() == type || type == -1)
- && (hasHints(item, hints) && !hasAnyHints(item, nonHints))).findFirst()
- .orElse(null);
+ public static SliceItem findSubtype(SliceItem s, final String format, final String subtype) {
+ return stream(s).filter(new Predicate<SliceItem>() {
+ @Override
+ public boolean test(SliceItem item) {
+ return checkFormat(item, format) && checkSubtype(item, subtype);
+ }
+ }).findFirst().orElse(null);
+ }
+
+ /**
+ */
+ public static SliceItem find(SliceItem s, final String format, final String[] hints,
+ final String[] nonHints) {
+ return stream(s).filter(new Predicate<SliceItem>() {
+ @Override
+ public boolean test(SliceItem item) {
+ return checkFormat(item, format)
+ && (hasHints(item, hints) && !hasAnyHints(item, nonHints));
+ }
+ }).findFirst().orElse(null);
+ }
+
+ private static boolean checkFormat(SliceItem item, String format) {
+ return format == null || format.equals(item.getFormat());
+ }
+
+ private static boolean checkSubtype(SliceItem item, String subtype) {
+ return subtype == null || subtype.equals(item.getSubType());
}
/**
@@ -267,7 +321,7 @@ public class SliceQuery {
/**
*/
- private static Stream<SliceItem> getSliceItemStream(Queue<SliceItem> items) {
+ private static Stream<SliceItem> getSliceItemStream(final Queue<SliceItem> items) {
Iterator<SliceItem> iterator = new Iterator<SliceItem>() {
@Override
public boolean hasNext() {
@@ -277,8 +331,8 @@ public class SliceQuery {
@Override
public SliceItem next() {
SliceItem item = items.poll();
- if (item.getType() == SliceItem.TYPE_SLICE
- || item.getType() == SliceItem.TYPE_ACTION) {
+ if (FORMAT_SLICE.equals(item.getFormat())
+ || FORMAT_ACTION.equals(item.getFormat())) {
items.addAll(item.getSlice().getItems());
}
return item;
diff --git a/androidx/app/slice/core/SliceSpecs.java b/androidx/app/slice/core/SliceSpecs.java
new file mode 100644
index 00000000..a21633ba
--- /dev/null
+++ b/androidx/app/slice/core/SliceSpecs.java
@@ -0,0 +1,33 @@
+/*
+ * 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 androidx.app.slice.core;
+
+import android.app.slice.SliceSpec;
+import android.support.annotation.RestrictTo;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SliceSpecs {
+
+ // TODO: Fill these in.
+ public static List<SliceSpec> SUPPORTED_SPECS = Collections.emptyList();
+}
diff --git a/androidx/app/slice/widget/ActionRow.java b/androidx/app/slice/widget/ActionRow.java
index 0a09620e..70cec4fe 100644
--- a/androidx/app/slice/widget/ActionRow.java
+++ b/androidx/app/slice/widget/ActionRow.java
@@ -16,11 +16,16 @@
package androidx.app.slice.widget;
+import static android.app.slice.Slice.HINT_NO_TINT;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_COLOR;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;
+
+import android.annotation.TargetApi;
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.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
@@ -36,12 +41,16 @@ import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.TextView;
+import java.util.function.Consumer;
+
+import androidx.app.slice.SliceItem;
import androidx.app.slice.core.SliceQuery;
/**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(24)
public class ActionRow extends FrameLayout {
private static final int MAX_ACTIONS = 5;
@@ -70,7 +79,7 @@ public class ActionRow extends FrameLayout {
for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
View view = mActionsGroup.getChildAt(i);
SliceItem item = (SliceItem) view.getTag();
- boolean tint = !item.hasHint(Slice.HINT_NO_TINT);
+ boolean tint = !item.hasHint(HINT_NO_TINT);
if (tint) {
((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor));
}
@@ -100,36 +109,54 @@ public class ActionRow extends FrameLayout {
mActionsGroup.removeAllViews();
addView(mActionsGroup);
- SliceItem color = SliceQuery.find(actionRow, SliceItem.TYPE_COLOR);
+ SliceItem color = SliceQuery.find(actionRow, FORMAT_COLOR);
if (color == null) {
color = defColor;
}
if (color != null) {
setColor(color.getColor());
}
- SliceQuery.findAll(actionRow, SliceItem.TYPE_ACTION).forEach(action -> {
- if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
- return;
- }
- SliceItem image = SliceQuery.find(action, SliceItem.TYPE_IMAGE);
- if (image == null) {
- return;
- }
- boolean tint = !image.hasHint(Slice.HINT_NO_TINT);
- SliceItem input = SliceQuery.find(action, SliceItem.TYPE_REMOTE_INPUT);
- if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
- addAction(image.getIcon(), tint, image).setOnClickListener(
- v -> handleRemoteInputClick(v, action.getAction(), input.getRemoteInput()));
- createRemoteInputView(mColor, getContext());
- } else {
- addAction(image.getIcon(), tint, image).setOnClickListener(v -> AsyncTask.execute(
- () -> {
- try {
- action.getAction().send();
- } catch (CanceledException e) {
- e.printStackTrace();
- }
- }));
+ SliceQuery.findAll(actionRow, FORMAT_ACTION).forEach(new Consumer<SliceItem>() {
+ @Override
+ public void accept(final SliceItem action) {
+ if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
+ return;
+ }
+ SliceItem image = SliceQuery.find(action, FORMAT_IMAGE);
+ if (image == null) {
+ return;
+ }
+ boolean tint = !image.hasHint(HINT_NO_TINT);
+ final SliceItem input = SliceQuery.find(action, FORMAT_REMOTE_INPUT);
+ if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
+ addAction(image.getIcon(), tint, image).setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ handleRemoteInputClick(v, action.getAction(),
+ input.getRemoteInput());
+ }
+ });
+ createRemoteInputView(mColor, getContext());
+ } else {
+ addAction(image.getIcon(), tint, image).setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+
+ try {
+ action.getAction().send();
+ } catch (CanceledException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+ });
+ }
}
});
setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
diff --git a/androidx/app/slice/widget/GridView.java b/androidx/app/slice/widget/GridView.java
index c320c728..2aa57bd4 100644
--- a/androidx/app/slice/widget/GridView.java
+++ b/androidx/app/slice/widget/GridView.java
@@ -16,11 +16,16 @@
package androidx.app.slice.widget;
+import static android.app.slice.Slice.HINT_LARGE;
+import static android.app.slice.Slice.HINT_TITLE;
+import static android.app.slice.SliceItem.FORMAT_COLOR;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
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.annotation.TargetApi;
import android.content.Context;
import android.graphics.Color;
import android.support.annotation.RestrictTo;
@@ -37,7 +42,9 @@ import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
+import androidx.app.slice.SliceItem;
import androidx.app.slice.core.SliceQuery;
import androidx.app.slice.view.R;
@@ -45,6 +52,7 @@ import androidx.app.slice.view.R;
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(24)
public class GridView extends LinearLayout implements LargeSliceAdapter.SliceListView {
private static final String TAG = "GridView";
@@ -76,7 +84,7 @@ public class GridView extends LinearLayout implements LargeSliceAdapter.SliceLis
mIsAllImages = true;
removeAllViews();
int total = 1;
- if (slice.getType() == SliceItem.TYPE_SLICE) {
+ if (FORMAT_SLICE.equals(slice.getFormat())) {
List<SliceItem> items = slice.getSlice().getItems();
total = items.size();
for (int i = 0; i < total; i++) {
@@ -100,6 +108,11 @@ public class GridView extends LinearLayout implements LargeSliceAdapter.SliceLis
}
}
+ @Override
+ public void setColor(SliceItem color) {
+
+ }
+
private void addExtraCount(int numExtra) {
View last = getChildAt(getChildCount() - 1);
FrameLayout frame = new FrameLayout(getContext());
@@ -126,15 +139,15 @@ public class GridView extends LinearLayout implements LargeSliceAdapter.SliceLis
/**
* Returns true if this item is just an image.
*/
- private boolean addItem(SliceItem item) {
- if (item.getType() == SliceItem.TYPE_IMAGE) {
+ private boolean addItem(final SliceItem item) {
+ if (FORMAT_IMAGE.equals(item.getFormat())) {
ImageView v = new ImageView(getContext());
v.setImageIcon(item.getIcon());
v.setScaleType(ScaleType.CENTER_CROP);
addView(v, new LayoutParams(0, MATCH_PARENT, 1));
return true;
} else {
- LinearLayout v = new LinearLayout(getContext());
+ final LinearLayout v = new LinearLayout(getContext());
int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
12, getContext().getResources().getDisplayMetrics());
v.setPadding(0, s, 0, 0);
@@ -142,40 +155,45 @@ public class GridView extends LinearLayout implements LargeSliceAdapter.SliceLis
v.setGravity(Gravity.CENTER_HORIZONTAL);
// TODO: Unify sporadic inflates that happen throughout the code.
ArrayList<SliceItem> items = new ArrayList<>();
- if (item.getType() == SliceItem.TYPE_SLICE) {
+ if (FORMAT_SLICE.equals(item.getFormat())) {
items.addAll(item.getSlice().getItems());
}
- items.forEach(i -> {
- Context context = getContext();
- switch (i.getType()) {
- case SliceItem.TYPE_TEXT:
- boolean title = false;
- if ((SliceQuery.hasAnyHints(item, new String[] {
- Slice.HINT_LARGE, Slice.HINT_TITLE
- }))) {
- title = true;
- }
- TextView tv = (TextView) LayoutInflater.from(context).inflate(title
- ? R.layout.abc_slice_title : R.layout.abc_slice_secondary_text,
- null);
- tv.setText(i.getText());
- v.addView(tv);
- break;
- case SliceItem.TYPE_IMAGE:
- ImageView iv = new ImageView(context);
- iv.setImageIcon(i.getIcon());
- if (item.hasHint(Slice.HINT_LARGE)) {
- iv.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- } else {
- int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- 48, context.getResources().getDisplayMetrics());
- iv.setLayoutParams(new LayoutParams(size, size));
- }
- v.addView(iv);
- break;
- case SliceItem.TYPE_COLOR:
- // TODO: Support color to tint stuff here.
- break;
+ items.forEach(new Consumer<SliceItem>() {
+ @Override
+ public void accept(SliceItem i) {
+ Context context = getContext();
+ switch (i.getFormat()) {
+ case FORMAT_TEXT:
+ boolean title = false;
+ if ((SliceQuery.hasAnyHints(item, new String[]{
+ HINT_LARGE, HINT_TITLE
+ }))) {
+ title = true;
+ }
+ TextView tv = (TextView) LayoutInflater.from(context).inflate(title
+ ? R.layout.abc_slice_title
+ : R.layout.abc_slice_secondary_text,
+ null);
+ tv.setText(i.getText());
+ v.addView(tv);
+ break;
+ case FORMAT_IMAGE:
+ ImageView iv = new ImageView(context);
+ iv.setImageIcon(i.getIcon());
+ if (item.hasHint(HINT_LARGE)) {
+ iv.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ } else {
+ int size = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ 48, context.getResources().getDisplayMetrics());
+ iv.setLayoutParams(new LayoutParams(size, size));
+ }
+ v.addView(iv);
+ break;
+ case FORMAT_COLOR:
+ // TODO: Support color to tint stuff here.
+ break;
+ }
}
});
addView(v, new LayoutParams(0, WRAP_CONTENT, 1));
diff --git a/androidx/app/slice/widget/LargeSliceAdapter.java b/androidx/app/slice/widget/LargeSliceAdapter.java
index ff513c5f..335eb23f 100644
--- a/androidx/app/slice/widget/LargeSliceAdapter.java
+++ b/androidx/app/slice/widget/LargeSliceAdapter.java
@@ -16,8 +16,14 @@
package androidx.app.slice.widget;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
+import static android.app.slice.Slice.HINT_HORIZONTAL;
+import static android.app.slice.Slice.SUBTYPE_MESSAGE;
+import static android.app.slice.Slice.SUBTYPE_SOURCE;
+import static android.app.slice.SliceItem.FORMAT_COLOR;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+
+import android.annotation.TargetApi;
import android.content.Context;
import android.support.annotation.RestrictTo;
import android.support.v7.widget.RecyclerView;
@@ -29,8 +35,11 @@ import android.view.ViewGroup.LayoutParams;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.stream.Collectors;
+import androidx.app.slice.SliceItem;
import androidx.app.slice.core.SliceQuery;
import androidx.app.slice.view.R;
@@ -38,6 +47,7 @@ import androidx.app.slice.view.R;
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(24)
public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.SliceViewHolder> {
public static final int TYPE_DEFAULT = 1;
@@ -62,8 +72,12 @@ public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.Sl
public void setSliceItems(List<SliceItem> slices, SliceItem color) {
mColor = color;
mIdGen.resetUsage();
- mSlices = slices.stream().map(s -> new SliceWrapper(s, mIdGen))
- .collect(Collectors.toList());
+ mSlices = slices.stream().map(new Function<SliceItem, SliceWrapper>() {
+ @Override
+ public SliceWrapper apply(SliceItem s) {
+ return new SliceWrapper(s, mIdGen);
+ }
+ }).collect(Collectors.<SliceWrapper>toList());
notifyDataSetChanged();
}
@@ -118,20 +132,20 @@ public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.Sl
public SliceWrapper(SliceItem item, IdGenerator idGen) {
mItem = item;
- mType = getType(item);
+ mType = getFormat(item);
mId = idGen.getId(item);
}
- public static int getType(SliceItem item) {
- if (item.hasHint(Slice.HINT_MESSAGE)) {
+ public static int getFormat(SliceItem item) {
+ if (SUBTYPE_MESSAGE.equals(item.getSubType())) {
// TODO: Better way to determine me or not? Something more like Messaging style.
- if (SliceQuery.find(item, -1, Slice.HINT_SOURCE, null) != null) {
+ if (SliceQuery.findSubtype(item, null, SUBTYPE_SOURCE) != null) {
return TYPE_MESSAGE;
} else {
return TYPE_MESSAGE_LOCAL;
}
}
- if (item.hasHint(Slice.HINT_HORIZONTAL)) {
+ if (item.hasHint(HINT_HORIZONTAL)) {
return TYPE_GRID;
}
return TYPE_DEFAULT;
@@ -162,9 +176,7 @@ public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.Sl
/**
* Set the color for the items in this view.
*/
- default void setColor(SliceItem color) {
-
- }
+ void setColor(SliceItem color);
}
private static class IdGenerator {
@@ -184,21 +196,24 @@ public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.Sl
}
private String genString(SliceItem item) {
- StringBuilder builder = new StringBuilder();
- SliceQuery.stream(item).forEach(i -> {
- builder.append(i.getType());
- //i.removeHint(Slice.HINT_SELECTED);
- builder.append(i.getHints());
- switch (i.getType()) {
- case SliceItem.TYPE_IMAGE:
- builder.append(i.getIcon());
- break;
- case SliceItem.TYPE_TEXT:
- builder.append(i.getText());
- break;
- case SliceItem.TYPE_COLOR:
- builder.append(i.getColor());
- break;
+ final StringBuilder builder = new StringBuilder();
+ SliceQuery.stream(item).forEach(new Consumer<SliceItem>() {
+ @Override
+ public void accept(SliceItem i) {
+ builder.append(i.getFormat());
+ //i.removeHint(Slice.HINT_SELECTED);
+ builder.append(i.getHints());
+ switch (i.getFormat()) {
+ case FORMAT_IMAGE:
+ builder.append(i.getIcon());
+ break;
+ case FORMAT_TEXT:
+ builder.append(i.getText());
+ break;
+ case FORMAT_COLOR:
+ builder.append(i.getColor());
+ break;
+ }
}
});
return builder.toString();
diff --git a/androidx/app/slice/widget/LargeTemplateView.java b/androidx/app/slice/widget/LargeTemplateView.java
index c5e82aaf..0160f59f 100644
--- a/androidx/app/slice/widget/LargeTemplateView.java
+++ b/androidx/app/slice/widget/LargeTemplateView.java
@@ -16,10 +16,15 @@
package androidx.app.slice.widget;
+import static android.app.slice.Slice.HINT_ACTIONS;
+import static android.app.slice.Slice.HINT_LIST;
+import static android.app.slice.Slice.HINT_LIST_ITEM;
+import static android.app.slice.Slice.HINT_PARTIAL;
+import static android.app.slice.SliceItem.FORMAT_COLOR;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
+import android.annotation.TargetApi;
import android.content.Context;
import android.support.annotation.RestrictTo;
import android.support.v7.widget.LinearLayoutManager;
@@ -28,13 +33,17 @@ import android.util.TypedValue;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
import androidx.app.slice.core.SliceQuery;
/**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(24)
public class LargeTemplateView extends SliceView.SliceModeView {
private final LargeSliceAdapter mAdapter;
@@ -68,7 +77,7 @@ public class LargeTemplateView extends SliceView.SliceModeView {
mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mRecyclerView.getMeasuredHeight() > mMaxHeight
- || (mSlice != null && SliceQuery.hasHints(mSlice, Slice.HINT_PARTIAL))) {
+ || (mSlice != null && SliceQuery.hasHints(mSlice, HINT_PARTIAL))) {
mRecyclerView.getLayoutParams().height = mDefaultHeight;
} else {
mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
@@ -78,28 +87,31 @@ public class LargeTemplateView extends SliceView.SliceModeView {
@Override
public void setSlice(Slice slice) {
- SliceItem color = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+ SliceItem color = SliceQuery.find(slice, FORMAT_COLOR);
mSlice = slice;
- List<SliceItem> items = new ArrayList<>();
- boolean[] hasHeader = new boolean[1];
- if (SliceQuery.hasHints(slice, Slice.HINT_LIST)) {
+ final List<SliceItem> items = new ArrayList<>();
+ final boolean[] hasHeader = new boolean[1];
+ if (SliceQuery.hasHints(slice, HINT_LIST)) {
addList(slice, items);
} else {
- slice.getItems().forEach(item -> {
- if (item.hasHint(Slice.HINT_ACTIONS)) {
- return;
- } else if (item.getType() == SliceItem.TYPE_COLOR) {
- return;
- } else if (item.getType() == SliceItem.TYPE_SLICE
- && item.hasHint(Slice.HINT_LIST)) {
- addList(item.getSlice(), items);
- } else if (item.hasHint(Slice.HINT_LIST_ITEM)) {
- items.add(item);
- } else if (!hasHeader[0]) {
- hasHeader[0] = true;
- items.add(0, item);
- } else {
- items.add(item);
+ slice.getItems().forEach(new Consumer<SliceItem>() {
+ @Override
+ public void accept(SliceItem item) {
+ if (item.hasHint(HINT_ACTIONS)) {
+ return;
+ } else if (FORMAT_COLOR.equals(item.getFormat())) {
+ return;
+ } else if (FORMAT_SLICE.equals(item.getFormat())
+ && item.hasHint(HINT_LIST)) {
+ addList(item.getSlice(), items);
+ } else if (item.hasHint(HINT_LIST_ITEM)) {
+ items.add(item);
+ } else if (!hasHeader[0]) {
+ hasHeader[0] = true;
+ items.add(0, item);
+ } else {
+ items.add(item);
+ }
}
});
}
diff --git a/androidx/app/slice/widget/MessageView.java b/androidx/app/slice/widget/MessageView.java
index 9db35bb3..cb9497a8 100644
--- a/androidx/app/slice/widget/MessageView.java
+++ b/androidx/app/slice/widget/MessageView.java
@@ -16,8 +16,11 @@
package androidx.app.slice.widget;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
+import static android.app.slice.Slice.SUBTYPE_SOURCE;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+
+import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -30,12 +33,16 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import java.util.function.Consumer;
+
+import androidx.app.slice.SliceItem;
import androidx.app.slice.core.SliceQuery;
/**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(24)
public class MessageView extends LinearLayout implements LargeSliceAdapter.SliceListView {
private TextView mDetails;
@@ -54,7 +61,7 @@ public class MessageView extends LinearLayout implements LargeSliceAdapter.Slice
@Override
public void setSliceItem(SliceItem slice) {
- SliceItem source = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_SOURCE, null);
+ SliceItem source = SliceQuery.findSubtype(slice, FORMAT_IMAGE, SUBTYPE_SOURCE);
if (source != null) {
final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
24, getContext().getResources().getDisplayMetrics());
@@ -66,14 +73,22 @@ public class MessageView extends LinearLayout implements LargeSliceAdapter.Slice
d.draw(iconCanvas);
mIcon.setImageBitmap(SliceViewUtil.getCircularBitmap(iconBm));
}
- SpannableStringBuilder builder = new SpannableStringBuilder();
- SliceQuery.findAll(slice, SliceItem.TYPE_TEXT).forEach(text -> {
- if (builder.length() != 0) {
- builder.append('\n');
+ final SpannableStringBuilder builder = new SpannableStringBuilder();
+ SliceQuery.findAll(slice, FORMAT_TEXT).forEach(new Consumer<SliceItem>() {
+ @Override
+ public void accept(SliceItem text) {
+ if (builder.length() != 0) {
+ builder.append('\n');
+ }
+ builder.append(text.getText());
}
- builder.append(text.getText());
});
mDetails.setText(builder.toString());
}
+ @Override
+ public void setColor(SliceItem color) {
+
+ }
+
}
diff --git a/androidx/app/slice/widget/RemoteInputView.java b/androidx/app/slice/widget/RemoteInputView.java
index 9d45501d..da350183 100644
--- a/androidx/app/slice/widget/RemoteInputView.java
+++ b/androidx/app/slice/widget/RemoteInputView.java
@@ -17,6 +17,7 @@
package androidx.app.slice.widget;
import android.animation.Animator;
+import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Context;
@@ -56,6 +57,7 @@ import androidx.app.slice.view.R;
*/
// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(23)
public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
private static final String TAG = "RemoteInput";
diff --git a/androidx/app/slice/widget/ShortcutView.java b/androidx/app/slice/widget/ShortcutView.java
index b6e77cd0..f93abc6d 100644
--- a/androidx/app/slice/widget/ShortcutView.java
+++ b/androidx/app/slice/widget/ShortcutView.java
@@ -16,10 +16,17 @@
package androidx.app.slice.widget;
+import static android.app.slice.Slice.HINT_LARGE;
+import static android.app.slice.Slice.HINT_TITLE;
+import static android.app.slice.Slice.SUBTYPE_SOURCE;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_COLOR;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+
+import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -33,6 +40,8 @@ import android.graphics.drawable.shapes.OvalShape;
import android.net.Uri;
import android.support.annotation.RestrictTo;
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
import androidx.app.slice.core.SliceQuery;
import androidx.app.slice.view.R;
@@ -40,6 +49,7 @@ import androidx.app.slice.view.R;
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(23)
public class ShortcutView extends androidx.app.slice.widget.SliceView.SliceModeView {
private static final String TAG = "ShortcutView";
@@ -61,11 +71,14 @@ public class ShortcutView extends androidx.app.slice.widget.SliceView.SliceModeV
@Override
public void setSlice(Slice slice) {
+ mLabel = null;
+ mIcon = null;
+ mAction = null;
removeAllViews();
determineShortcutItems(getContext(), slice);
- SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+ SliceItem colorItem = SliceQuery.find(slice, FORMAT_COLOR);
if (colorItem == null) {
- colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+ colorItem = SliceQuery.find(slice, FORMAT_COLOR);
}
// TODO: pick better default colour
final int color = colorItem != null ? colorItem.getColor() : Color.GRAY;
@@ -73,8 +86,8 @@ public class ShortcutView extends androidx.app.slice.widget.SliceView.SliceModeV
circle.setTint(color);
setBackground(circle);
if (mIcon != null) {
- final boolean isLarge = mIcon.hasHint(Slice.HINT_LARGE)
- || mIcon.hasHint(Slice.HINT_SOURCE);
+ final boolean isLarge = mIcon.hasHint(HINT_LARGE)
+ || SUBTYPE_SOURCE.equals(mIcon.getSubType());
final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
SliceViewUtil.createCircledIcon(getContext(), color, iconSize, mIcon.getIcon(),
isLarge, this /* parent */);
@@ -112,38 +125,38 @@ public class ShortcutView extends androidx.app.slice.widget.SliceView.SliceModeV
* Looks at the slice and determines which items are best to use to compose the shortcut.
*/
private void determineShortcutItems(Context context, Slice slice) {
- SliceItem titleItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION,
- Slice.HINT_TITLE, null);
+ SliceItem titleItem = SliceQuery.find(slice, FORMAT_ACTION,
+ HINT_TITLE, null);
if (titleItem != null) {
// Preferred case: hinted action containing hinted image and text
mAction = titleItem.getAction();
- mIcon = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
+ mIcon = SliceQuery.find(titleItem.getSlice(), FORMAT_IMAGE, HINT_TITLE,
null);
- mLabel = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+ mLabel = SliceQuery.find(titleItem.getSlice(), FORMAT_TEXT, HINT_TITLE,
null);
} else {
// No hinted action; just use the first one
- SliceItem actionItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION, (String) null,
+ SliceItem actionItem = SliceQuery.find(slice, FORMAT_ACTION, (String) null,
null);
mAction = (actionItem != null) ? actionItem.getAction() : null;
}
// First fallback: any hinted image and text
if (mIcon == null) {
- mIcon = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
+ mIcon = SliceQuery.find(slice, FORMAT_IMAGE, HINT_TITLE,
null);
}
if (mLabel == null) {
- mLabel = SliceQuery.find(slice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+ mLabel = SliceQuery.find(slice, FORMAT_TEXT, HINT_TITLE,
null);
}
// Second fallback: first image and text
if (mIcon == null) {
- mIcon = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, (String) null,
+ mIcon = SliceQuery.find(slice, FORMAT_IMAGE, (String) null,
null);
}
if (mLabel == null) {
- mLabel = SliceQuery.find(slice, SliceItem.TYPE_TEXT, (String) null,
+ mLabel = SliceQuery.find(slice, FORMAT_TEXT, (String) null,
null);
}
// Final fallback: use app info
@@ -156,12 +169,12 @@ public class ShortcutView extends androidx.app.slice.widget.SliceView.SliceModeV
if (mIcon == null) {
Slice.Builder sb = new Slice.Builder(slice.getUri());
Drawable icon = pm.getApplicationIcon(appInfo);
- sb.addIcon(SliceViewUtil.createIconFromDrawable(icon), Slice.HINT_LARGE);
+ sb.addIcon(SliceViewUtil.createIconFromDrawable(icon), HINT_LARGE);
mIcon = sb.build().getItems().get(0);
}
if (mLabel == null) {
Slice.Builder sb = new Slice.Builder(slice.getUri());
- sb.addText(pm.getApplicationLabel(appInfo));
+ sb.addText(pm.getApplicationLabel(appInfo), null);
mLabel = sb.build().getItems().get(0);
}
if (mAction == null) {
diff --git a/androidx/app/slice/widget/SliceLiveData.java b/androidx/app/slice/widget/SliceLiveData.java
index eaaef502..9b36ee12 100644
--- a/androidx/app/slice/widget/SliceLiveData.java
+++ b/androidx/app/slice/widget/SliceLiveData.java
@@ -15,13 +15,16 @@
*/
package androidx.app.slice.widget;
-import android.app.slice.Slice;
import android.arch.lifecycle.LiveData;
import android.content.Context;
+import android.content.Intent;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
+import android.support.annotation.NonNull;
+
+import androidx.app.slice.Slice;
/**
* Class with factory methods for creating LiveData that observes slices.
@@ -40,36 +43,68 @@ public final class SliceLiveData {
return new SliceLiveDataImpl(context.getApplicationContext(), uri);
}
+ /**
+ * Produces an {@link LiveData} that tracks a Slice for a given Intent. To use
+ * this method your app must have the permission to the slice Uri or hold
+ * {@link android.Manifest.permission#BIND_SLICE}).
+ */
+ public static LiveData<Slice> fromIntent(@NonNull Context context, @NonNull Intent intent) {
+ return new SliceLiveDataImpl(context.getApplicationContext(), intent);
+ }
+
private static class SliceLiveDataImpl extends LiveData<Slice> {
- private final Uri mUri;
private final Context mContext;
+ private final Intent mIntent;
+ private Uri mUri;
private SliceLiveDataImpl(Context context, Uri uri) {
super();
mContext = context;
mUri = uri;
+ mIntent = null;
// TODO: Check if uri points at a Slice?
}
+ private SliceLiveDataImpl(Context context, Intent intent) {
+ super();
+ mContext = context;
+ mUri = null;
+ mIntent = intent;
+ }
+
@Override
protected void onActive() {
- AsyncTask.execute(this::updateSlice);
- mContext.getContentResolver().registerContentObserver(mUri, false, mObserver);
+ AsyncTask.execute(mUpdateSlice);
+ if (mUri != null) {
+ mContext.getContentResolver().registerContentObserver(mUri, false, mObserver);
+ }
}
@Override
protected void onInactive() {
- mContext.getContentResolver().unregisterContentObserver(mObserver);
+ if (mUri != null) {
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ }
}
- private void updateSlice() {
- postValue(Slice.bindSlice(mContext.getContentResolver(), mUri));
- }
+ private final Runnable mUpdateSlice = new Runnable() {
+ @Override
+ public void run() {
+ Slice s = mUri != null ? Slice.bindSlice(mContext, mUri)
+ : Slice.bindSlice(mContext, mIntent);
+ if (mUri == null && s != null) {
+ mContext.getContentResolver().registerContentObserver(s.getUri(),
+ false, mObserver);
+ mUri = s.getUri();
+ }
+ postValue(s);
+ }
+ };
private final ContentObserver mObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
- AsyncTask.execute(SliceLiveDataImpl.this::updateSlice);
+ AsyncTask.execute(mUpdateSlice);
}
};
}
diff --git a/androidx/app/slice/widget/SliceView.java b/androidx/app/slice/widget/SliceView.java
index 6e12dbab..5597ac9d 100644
--- a/androidx/app/slice/widget/SliceView.java
+++ b/androidx/app/slice/widget/SliceView.java
@@ -16,8 +16,10 @@
package androidx.app.slice.widget;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
+import static android.app.slice.Slice.HINT_ACTIONS;
+import static android.app.slice.SliceItem.FORMAT_COLOR;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
+
import android.arch.lifecycle.Observer;
import android.content.ContentResolver;
import android.content.Context;
@@ -34,6 +36,8 @@ import android.widget.FrameLayout;
import java.util.List;
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
import androidx.app.slice.core.SliceQuery;
import androidx.app.slice.view.R;
@@ -54,8 +58,9 @@ import androidx.app.slice.view.R;
* <p>
* When constructing a slice, the contents of it can be annotated with hints, these provide the OS
* with some information on how the content should be displayed. For example, text annotated with
- * {@link Slice#HINT_TITLE} would be placed in the title position of a template. A slice annotated
- * with {@link Slice#HINT_LIST} would present the child items of that slice in a list.
+ * {@link android.app.slice.Slice#HINT_TITLE} would be placed in the title position of a template.
+ * A slice annotated with {@link android.app.slice.Slice#HINT_LIST} would present the child items
+ * of that slice in a list.
* <p>
* Example usage:
*
@@ -111,8 +116,8 @@ public class SliceView extends ViewGroup implements Observer<Slice> {
public static final int MODE_LARGE = 2;
/**
* Mode indicating this slice should be presented as an icon. A shortcut requires an intent,
- * icon, and label. This can be indicated by using {@link Slice#HINT_TITLE} on an action in a
- * slice.
+ * icon, and label. This can be indicated by using {@link android.app.slice.Slice#HINT_TITLE}
+ * on an action in a slice.
*/
public static final int MODE_SHORTCUT = 3;
@@ -261,13 +266,15 @@ public class SliceView extends ViewGroup implements Observer<Slice> {
return;
}
// TODO: Smarter mapping here from one state to the next.
- SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
+ SliceItem color = SliceQuery.find(mCurrentSlice, FORMAT_COLOR);
List<SliceItem> items = mCurrentSlice.getItems();
- SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
- Slice.HINT_ACTIONS,
+ SliceItem actionRow = SliceQuery.find(mCurrentSlice, FORMAT_SLICE,
+ HINT_ACTIONS,
null);
int mode = getMode();
- if (mode != mCurrentView.getMode()) {
+ if (mMode == mCurrentView.getMode()) {
+ mCurrentView.setSlice(mCurrentSlice);
+ } else {
removeAllViews();
mCurrentView = createView(mode);
addView(mCurrentView, getChildLp(mCurrentView));
diff --git a/androidx/app/slice/widget/SliceViewUtil.java b/androidx/app/slice/widget/SliceViewUtil.java
index 62844c1b..c98215f3 100644
--- a/androidx/app/slice/widget/SliceViewUtil.java
+++ b/androidx/app/slice/widget/SliceViewUtil.java
@@ -16,6 +16,7 @@
package androidx.app.slice.widget;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
@@ -48,6 +49,7 @@ import java.util.Calendar;
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(23)
public class SliceViewUtil {
/**
@@ -152,6 +154,7 @@ public class SliceViewUtil {
/**
*/
+ @TargetApi(28)
public static void createCircledIcon(@NonNull Context context, int color, int iconSizePx,
Icon icon, boolean isLarge, ViewGroup parent) {
ImageView v = new ImageView(context);
diff --git a/androidx/app/slice/widget/SmallTemplateView.java b/androidx/app/slice/widget/SmallTemplateView.java
index f430602d..1d3ab2d8 100644
--- a/androidx/app/slice/widget/SmallTemplateView.java
+++ b/androidx/app/slice/widget/SmallTemplateView.java
@@ -16,16 +16,29 @@
package androidx.app.slice.widget;
+import static android.app.slice.Slice.HINT_LIST;
+import static android.app.slice.Slice.HINT_LIST_ITEM;
+import static android.app.slice.Slice.HINT_NO_TINT;
+import static android.app.slice.Slice.HINT_SELECTED;
+import static android.app.slice.Slice.HINT_TITLE;
+import static android.app.slice.SliceItem.FORMAT_ACTION;
+import static android.app.slice.SliceItem.FORMAT_COLOR;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_SLICE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+import static android.app.slice.SliceItem.FORMAT_TIMESTAMP;
+
+import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.support.annotation.RestrictTo;
import android.util.Log;
import android.view.View;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Switch;
@@ -33,8 +46,11 @@ import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Predicate;
-import androidx.app.slice.builders.SliceHints;
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceHints;
import androidx.app.slice.core.SliceQuery;
import androidx.app.slice.view.R;
@@ -44,6 +60,7 @@ import androidx.app.slice.view.R;
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@TargetApi(23)
public class SmallTemplateView extends SliceView.SliceModeView implements
LargeSliceAdapter.SliceListView, View.OnClickListener {
@@ -73,7 +90,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.abc_slice_padding);
inflate(context, R.layout.abc_slice_small_template, this);
- mStartContainer = (LinearLayout) findViewById(android.R.id.icon_frame);
+ mStartContainer = (LinearLayout) findViewById(R.id.icon_frame);
mContent = (LinearLayout) findViewById(android.R.id.content);
mPrimaryText = (TextView) findViewById(android.R.id.title);
mSecondaryText = (TextView) findViewById(android.R.id.summary);
@@ -96,6 +113,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
public void setSliceItem(SliceItem slice) {
populateViews(slice, slice);
}
+
@Override
public void setSlice(Slice slice) {
Slice.Builder sb = new Slice.Builder(slice.getUri());
@@ -106,15 +124,15 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
private SliceItem getFirstSlice(Slice slice) {
List<SliceItem> items = slice.getItems();
- if (items.size() > 0 && items.get(0).getType() == SliceItem.TYPE_SLICE) {
+ if (items.size() > 0 && FORMAT_SLICE.equals(items.get(0).getFormat())) {
// Check if this slice is appropriate to use to populate small template
SliceItem firstSlice = items.get(0);
- if (firstSlice.hasHint(Slice.HINT_LIST)) {
+ if (firstSlice.hasHint(HINT_LIST)) {
// Check for header, use that if it exists
- SliceItem header = SliceQuery.find(firstSlice, SliceItem.TYPE_SLICE,
+ SliceItem header = SliceQuery.find(firstSlice, FORMAT_SLICE,
null,
new String[] {
- Slice.HINT_LIST_ITEM, Slice.HINT_LIST
+ HINT_LIST_ITEM, HINT_LIST
});
if (header != null) {
return SliceQuery.findFirstSlice(header);
@@ -134,10 +152,11 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
return s.getItems().get(0);
}
+ @TargetApi(24)
private void populateViews(SliceItem fullSlice, SliceItem sliceItem) {
resetViews();
ArrayList<SliceItem> items = new ArrayList<>();
- if (sliceItem.getType() == SliceItem.TYPE_SLICE) {
+ if (FORMAT_SLICE.equals(sliceItem.getFormat())) {
items = new ArrayList<>(sliceItem.getSlice().getItems());
} else {
items.add(sliceItem);
@@ -152,7 +171,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
// If the first item is an action let's check if it should be used to populate the content
// or if it should be in the start position.
SliceItem firstSlice = items.size() > 0 ? items.get(0) : null;
- if (firstSlice != null && firstSlice.getType() == SliceItem.TYPE_ACTION) {
+ if (firstSlice != null && FORMAT_ACTION.equals(firstSlice.getFormat())) {
if (!SliceQuery.isSimpleAction(firstSlice)) {
mRowAction = firstSlice;
items.remove(0);
@@ -168,19 +187,19 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
for (int i = 0; i < items.size(); i++) {
SliceItem item = items.get(i);
List<String> hints = item.getHints();
- int itemType = item.getType();
- if (hints.contains(Slice.HINT_TITLE)) {
+ String itemType = item.getFormat();
+ if (hints.contains(HINT_TITLE)) {
// Things with these hints could go in the title / start position
- if ((startItem == null || !startItem.hasHint(Slice.HINT_TITLE))
+ if ((startItem == null || !startItem.hasHint(HINT_TITLE))
&& SliceQuery.isStartType(item)) {
startItem = item;
- } else if ((titleItem == null || !titleItem.hasHint(Slice.HINT_TITLE))
- && itemType == SliceItem.TYPE_TEXT) {
+ } else if ((titleItem == null || !titleItem.hasHint(HINT_TITLE))
+ && FORMAT_TEXT.equals(itemType)) {
titleItem = item;
} else {
endItems.add(item);
}
- } else if (item.getType() == SliceItem.TYPE_TEXT) {
+ } else if (FORMAT_TEXT.equals(item.getFormat())) {
if (titleItem == null) {
titleItem = item;
} else if (subTitle == null) {
@@ -188,7 +207,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
} else {
endItems.add(item);
}
- } else if (item.getType() == SliceItem.TYPE_SLICE) {
+ } else if (FORMAT_SLICE.equals(item.getFormat())) {
List<SliceItem> subItems = item.getSlice().getItems();
for (int j = 0; j < subItems.size(); j++) {
endItems.add(subItems.get(j));
@@ -202,6 +221,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
if (startItem != null) {
// TODO - check for icon, timestamp, action with icon
}
+ mStartContainer.setVisibility(startItem != null ? View.VISIBLE : View.GONE);
if (titleItem != null) {
mPrimaryText.setText(titleItem.getText());
}
@@ -221,8 +241,13 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
}
// Check if we have a toggle somewhere in our end items
SliceItem toggleItem = endItems.stream()
- .filter(item -> (item.getType() == SliceItem.TYPE_ACTION
- && SliceQuery.hasHints(item.getSlice(), SliceHints.HINT_TOGGLE)))
+ .filter(new Predicate<SliceItem>() {
+ @Override
+ public boolean test(SliceItem item) {
+ return FORMAT_ACTION.equals(item.getFormat())
+ && SliceQuery.hasHints(item.getSlice(), SliceHints.HINT_TOGGLE);
+ }
+ })
.findFirst().orElse(null);
if (toggleItem != null) {
if (addToggle(toggleItem)) {
@@ -233,7 +258,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
}
}
// If we're here we can still show end items
- SliceItem colorItem = SliceQuery.find(fullSlice, SliceItem.TYPE_COLOR);
+ SliceItem colorItem = SliceQuery.find(fullSlice, FORMAT_COLOR);
int color = colorItem != null
? colorItem.getColor()
: (mColorItem != null)
@@ -244,7 +269,7 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
for (int i = 0; i < items.size(); i++) {
SliceItem item = items.get(i);
if (itemCount <= MAX_END_ITEMS) {
- if (item.getType() == SliceItem.TYPE_ACTION) {
+ if (FORMAT_ACTION.equals(item.getFormat())) {
if (SliceQuery.hasHints(item.getSlice(), SliceHints.HINT_TOGGLE)) {
if (addToggle(item)) {
break;
@@ -254,10 +279,10 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
clickableEndItem = true;
itemCount++;
}
- } else if (item.getType() == SliceItem.TYPE_IMAGE) {
+ } else if (FORMAT_IMAGE.equals(item.getFormat())) {
addIcon(item, color, mEndContainer);
itemCount++;
- } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
+ } else if (FORMAT_TIMESTAMP.equals(item.getFormat())) {
TextView tv = new TextView(getContext());
tv.setText(SliceViewUtil.getRelativeTimeString(item.getTimestamp()));
mEndContainer.addView(tv);
@@ -273,21 +298,24 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
/**
* @return Whether a toggle was added.
*/
- private boolean addToggle(SliceItem toggleItem) {
- if (toggleItem.getType() != SliceItem.TYPE_ACTION
+ private boolean addToggle(final SliceItem toggleItem) {
+ if (!FORMAT_ACTION.equals(toggleItem.getFormat())
|| !SliceQuery.hasHints(toggleItem.getSlice(), SliceHints.HINT_TOGGLE)) {
return false;
}
mToggle = new Switch(getContext());
mEndContainer.addView(mToggle);
- mToggle.setChecked(SliceQuery.hasHints(toggleItem.getSlice(), Slice.HINT_SELECTED));
- mToggle.setOnCheckedChangeListener((buttonView, isChecked) -> {
- try {
- PendingIntent pi = toggleItem.getAction();
- Intent i = new Intent().putExtra(SliceHints.EXTRA_TOGGLE_STATE, isChecked);
- pi.send(getContext(), 0, i, null, null);
- } catch (CanceledException e) {
- mToggle.setSelected(!isChecked);
+ mToggle.setChecked(SliceQuery.hasHints(toggleItem.getSlice(), HINT_SELECTED));
+ mToggle.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ try {
+ PendingIntent pi = toggleItem.getAction();
+ Intent i = new Intent().putExtra(SliceHints.EXTRA_TOGGLE_STATE, isChecked);
+ pi.send(getContext(), 0, i, null, null);
+ } catch (CanceledException e) {
+ mToggle.setSelected(!isChecked);
+ }
}
});
return true;
@@ -299,10 +327,10 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
private boolean addIcon(SliceItem sliceItem, int color, LinearLayout container) {
SliceItem image = null;
SliceItem action = null;
- if (sliceItem.getType() == SliceItem.TYPE_ACTION) {
- image = SliceQuery.find(sliceItem.getSlice(), SliceItem.TYPE_IMAGE);
+ if (FORMAT_ACTION.equals(sliceItem.getFormat())) {
+ image = SliceQuery.find(sliceItem.getSlice(), FORMAT_IMAGE);
action = sliceItem;
- } else if (sliceItem.getType() == SliceItem.TYPE_IMAGE) {
+ } else if (FORMAT_IMAGE.equals(sliceItem.getFormat())) {
image = sliceItem;
}
if (image != null) {
@@ -310,18 +338,25 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
iv.setImageIcon(image.getIcon());
if (action != null) {
final SliceItem sliceAction = action;
- iv.setOnClickListener(v -> AsyncTask.execute(
- () -> {
- try {
- sliceAction.getAction().send();
- } catch (CanceledException e) {
- e.printStackTrace();
+ iv.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ sliceAction.getAction().send();
+ } catch (CanceledException e) {
+ e.printStackTrace();
+ }
}
- }));
+ });
+ }
+ });
iv.setBackground(SliceViewUtil.getDrawable(getContext(),
android.R.attr.selectableItemBackground));
}
- if (color != -1 && !sliceItem.hasHint(Slice.HINT_NO_TINT)) {
+ if (color != -1 && !sliceItem.hasHint(HINT_NO_TINT)) {
iv.setColorFilter(color);
}
container.addView(iv);
@@ -336,17 +371,20 @@ public class SmallTemplateView extends SliceView.SliceModeView implements
@Override
public void onClick(View view) {
- if (mRowAction != null && mRowAction.getType() == SliceItem.TYPE_ACTION) {
+ if (mRowAction != null && FORMAT_ACTION.equals(mRowAction.getFormat())) {
if (mToggle != null
&& SliceQuery.hasHints(mRowAction.getSlice(), SliceHints.HINT_TOGGLE)) {
mToggle.toggle();
return;
}
- AsyncTask.execute(() -> {
- try {
- mRowAction.getAction().send();
- } catch (CanceledException e) {
- Log.w(TAG, "PendingIntent for slice cannot be sent", e);
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mRowAction.getAction().send();
+ } catch (CanceledException e) {
+ Log.w(TAG, "PendingIntent for slice cannot be sent", e);
+ }
}
});
}
diff --git a/com/android/car/setupwizardlib/CarSetupWizardLayout.java b/com/android/car/setupwizardlib/CarSetupWizardLayout.java
index d1838975..35863107 100644
--- a/com/android/car/setupwizardlib/CarSetupWizardLayout.java
+++ b/com/android/car/setupwizardlib/CarSetupWizardLayout.java
@@ -23,6 +23,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
+import android.widget.ProgressBar;
/**
@@ -41,6 +42,8 @@ public class CarSetupWizardLayout extends LinearLayout {
private Button mPrimaryContinueButton;
private Button mSecondaryContinueButton;
+ private ProgressBar mProgressBar;
+
public CarSetupWizardLayout(Context context) {
this(context, null);
}
@@ -84,6 +87,8 @@ public class CarSetupWizardLayout extends LinearLayout {
String secondaryContinueButtonText;
boolean secondaryContinueButtonEnabled;
+ boolean showProgressBar;
+
try {
showBackButton = attrArray.getBoolean(
R.styleable.CarSetupWizardLayout_showBackButton, true);
@@ -99,6 +104,8 @@ public class CarSetupWizardLayout extends LinearLayout {
R.styleable.CarSetupWizardLayout_secondaryContinueButtonText);
secondaryContinueButtonEnabled = attrArray.getBoolean(
R.styleable.CarSetupWizardLayout_secondaryContinueButtonEnabled, true);
+ showProgressBar = attrArray.getBoolean(
+ R.styleable.CarSetupWizardLayout_showProgressBar, false);
} finally {
attrArray.recycle();
}
@@ -130,14 +137,16 @@ public class CarSetupWizardLayout extends LinearLayout {
setSecondaryContinueButtonVisible(false);
}
- // TODO: Handle loading bar logic
+ mProgressBar = findViewById(R.id.progress_bar);
+ setProgressBarVisible(showProgressBar);
+
}
/**
* Set a given button's visibility.
*/
- private void setViewVisible(View button, boolean visible) {
- button.setVisibility(visible ? View.VISIBLE : View.GONE);
+ private void setViewVisible(View view, boolean visible) {
+ view.setVisibility(visible ? View.VISIBLE : View.GONE);
}
/**
@@ -212,4 +221,11 @@ public class CarSetupWizardLayout extends LinearLayout {
public void setSecondaryContinueButtonVisible(boolean visible) {
setViewVisible(mSecondaryContinueButton, visible);
}
+
+ /**
+ * Set the progress bar visibility to the given visibility.
+ */
+ public void setProgressBarVisible(boolean visible) {
+ setViewVisible(mProgressBar, visible);
+ }
}
diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java
deleted file mode 100644
index 9490880a..00000000
--- a/com/android/commands/pm/Pm.java
+++ /dev/null
@@ -1,822 +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 com.android.commands.pm;
-
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-
-import android.accounts.IAccountManager;
-import android.app.ActivityManager;
-import android.app.PackageInstallObserver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDataObserver;
-import android.content.pm.IPackageInstaller;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageInstaller.SessionParams;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.ApkLite;
-import android.content.pm.PackageParser.PackageLite;
-import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.UserInfo;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.IUserManager;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.SELinux;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.storage.StorageManager;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.content.PackageHelper;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.SizedInputStream;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.TimeUnit;
-
-public final class Pm {
- private static final String TAG = "Pm";
- private static final String STDIN_PATH = "-";
-
- IPackageManager mPm;
- IPackageInstaller mInstaller;
- IUserManager mUm;
- IAccountManager mAm;
-
- private String[] mArgs;
- private int mNextArg;
- private String mCurArgData;
-
- private static final String PM_NOT_RUNNING_ERR =
- "Error: Could not access the Package Manager. Is the system running?";
-
- public static void main(String[] args) {
- int exitCode = 1;
- try {
- exitCode = new Pm().run(args);
- } catch (Exception e) {
- Log.e(TAG, "Error", e);
- System.err.println("Error: " + e);
- if (e instanceof RemoteException) {
- System.err.println(PM_NOT_RUNNING_ERR);
- }
- }
- System.exit(exitCode);
- }
-
- public int run(String[] args) throws RemoteException {
- if (args.length < 1) {
- return runShellCommand("package", mArgs);
- }
- mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
- mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
- mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-
- if (mPm == null) {
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- mInstaller = mPm.getPackageInstaller();
-
- mArgs = args;
- String op = args[0];
- mNextArg = 1;
-
- if ("install".equals(op)) {
- return runInstall();
- }
-
- if ("install-create".equals(op)) {
- return runInstallCreate();
- }
-
- if ("install-write".equals(op)) {
- return runInstallWrite();
- }
-
- if ("install-commit".equals(op)) {
- return runInstallCommit();
- }
-
- if ("install-abandon".equals(op) || "install-destroy".equals(op)) {
- return runInstallAbandon();
- }
-
- return runShellCommand("package", mArgs);
- }
-
- static final class MyShellCallback extends ShellCallback {
- @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
- String mode) {
- File file = new File(path);
- final ParcelFileDescriptor fd;
- try {
- fd = ParcelFileDescriptor.open(file,
- ParcelFileDescriptor.MODE_CREATE |
- ParcelFileDescriptor.MODE_TRUNCATE |
- ParcelFileDescriptor.MODE_WRITE_ONLY);
- } catch (FileNotFoundException e) {
- String msg = "Unable to open file " + path + ": " + e;
- System.err.println(msg);
- throw new IllegalArgumentException(msg);
- }
- if (seLinuxContext != null) {
- final String tcon = SELinux.getFileContext(file.getAbsolutePath());
- if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) {
- try {
- fd.close();
- } catch (IOException e) {
- }
- String msg = "System server has no access to file context " + tcon;
- System.err.println(msg + " (from path " + file.getAbsolutePath()
- + ", context " + seLinuxContext + ")");
- throw new IllegalArgumentException(msg);
- }
- }
- return fd;
- }
- }
-
- private int runShellCommand(String serviceName, String[] args) {
- final HandlerThread handlerThread = new HandlerThread("results");
- handlerThread.start();
- try {
- ServiceManager.getService(serviceName).shellCommand(
- FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- args, new MyShellCallback(),
- new ResultReceiver(new Handler(handlerThread.getLooper())));
- return 0;
- } catch (RemoteException e) {
- e.printStackTrace();
- } finally {
- handlerThread.quitSafely();
- }
- return -1;
- }
-
- private static class LocalIntentReceiver {
- private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
-
- private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
- @Override
- public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
- IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
- try {
- mResult.offer(intent, 5, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- };
-
- public IntentSender getIntentSender() {
- return new IntentSender((IIntentSender) mLocalSender);
- }
-
- public Intent getResult() {
- try {
- return mResult.take();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- private int translateUserId(int userId, String logContext) {
- return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, true, true, logContext, "pm command");
- }
-
- private static String checkAbiArgument(String abi) {
- if (TextUtils.isEmpty(abi)) {
- throw new IllegalArgumentException("Missing ABI argument");
- }
- if ("-".equals(abi)) {
- return abi;
- }
- final String[] supportedAbis = Build.SUPPORTED_ABIS;
- for (String supportedAbi : supportedAbis) {
- if (supportedAbi.equals(abi)) {
- return abi;
- }
- }
- throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
- }
-
- /*
- * Keep this around to support existing users of the "pm install" command that may not be
- * able to be updated [or, at least informed the API has changed] such as ddmlib.
- *
- * Moving the implementation of "pm install" to "cmd package install" changes the executing
- * context. Instead of being a stand alone process, "cmd package install" runs in the
- * system_server process. Due to SELinux rules, system_server cannot access many directories;
- * one of which being the package install staging directory [/data/local/tmp].
- *
- * The use of "adb install" or "cmd package install" over "pm install" is highly encouraged.
- */
- private int runInstall() throws RemoteException {
- long startedTime = SystemClock.elapsedRealtime();
- final InstallParams params = makeInstallParams();
- final String inPath = nextArg();
- if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
- File file = new File(inPath);
- if (file.isFile()) {
- try {
- ApkLite baseApk = PackageParser.parseApkLite(file, 0);
- PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
- null, null);
- params.sessionParams.setSize(
- PackageHelper.calculateInstalledSize(pkgLite,
- params.sessionParams.abiOverride));
- } catch (PackageParserException | IOException e) {
- System.err.println("Error: Failed to parse APK file: " + e);
- return 1;
- }
- } else {
- System.err.println("Error: Can't open non-file: " + inPath);
- return 1;
- }
- }
-
- final int sessionId = doCreateSession(params.sessionParams,
- params.installerPackageName, params.userId);
-
- try {
- if (inPath == null && params.sessionParams.sizeBytes == -1) {
- System.err.println("Error: must either specify a package size or an APK file");
- return 1;
- }
- if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
- false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
- return 1;
- }
- Pair<String, Integer> status = doCommitSession(sessionId, false /*logSuccess*/);
- if (status.second != PackageInstaller.STATUS_SUCCESS) {
- return 1;
- }
- Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime()
- - startedTime) + " ms");
- System.out.println("Success");
- return 0;
- } finally {
- try {
- mInstaller.abandonSession(sessionId);
- } catch (Exception ignore) {
- }
- }
- }
-
- private int runInstallAbandon() throws RemoteException {
- final int sessionId = Integer.parseInt(nextArg());
- return doAbandonSession(sessionId, true /*logSuccess*/);
- }
-
- private int runInstallCommit() throws RemoteException {
- final int sessionId = Integer.parseInt(nextArg());
- return doCommitSession(sessionId, true /*logSuccess*/).second;
- }
-
- private int runInstallCreate() throws RemoteException {
- final InstallParams installParams = makeInstallParams();
- final int sessionId = doCreateSession(installParams.sessionParams,
- installParams.installerPackageName, installParams.userId);
-
- // NOTE: adb depends on parsing this string
- System.out.println("Success: created install session [" + sessionId + "]");
- return PackageInstaller.STATUS_SUCCESS;
- }
-
- private int runInstallWrite() throws RemoteException {
- long sizeBytes = -1;
-
- String opt;
- while ((opt = nextOption()) != null) {
- if (opt.equals("-S")) {
- sizeBytes = Long.parseLong(nextArg());
- } else {
- throw new IllegalArgumentException("Unknown option: " + opt);
- }
- }
-
- final int sessionId = Integer.parseInt(nextArg());
- final String splitName = nextArg();
- final String path = nextArg();
- return doWriteSession(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
- }
-
- private static class InstallParams {
- SessionParams sessionParams;
- String installerPackageName;
- int userId = UserHandle.USER_ALL;
- }
-
- private InstallParams makeInstallParams() {
- final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
- final InstallParams params = new InstallParams();
- params.sessionParams = sessionParams;
- String opt;
- while ((opt = nextOption()) != null) {
- switch (opt) {
- case "-l":
- sessionParams.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
- break;
- case "-r":
- sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
- break;
- case "-i":
- params.installerPackageName = nextArg();
- if (params.installerPackageName == null) {
- throw new IllegalArgumentException("Missing installer package");
- }
- break;
- case "-t":
- sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_TEST;
- break;
- case "-s":
- sessionParams.installFlags |= PackageManager.INSTALL_EXTERNAL;
- break;
- case "-f":
- sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL;
- break;
- case "-d":
- sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
- break;
- case "-g":
- sessionParams.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
- break;
- case "--dont-kill":
- sessionParams.installFlags |= PackageManager.INSTALL_DONT_KILL_APP;
- break;
- case "--originating-uri":
- sessionParams.originatingUri = Uri.parse(nextOptionData());
- break;
- case "--referrer":
- sessionParams.referrerUri = Uri.parse(nextOptionData());
- break;
- case "-p":
- sessionParams.mode = SessionParams.MODE_INHERIT_EXISTING;
- sessionParams.appPackageName = nextOptionData();
- if (sessionParams.appPackageName == null) {
- throw new IllegalArgumentException("Missing inherit package name");
- }
- break;
- case "--pkg":
- sessionParams.appPackageName = nextOptionData();
- if (sessionParams.appPackageName == null) {
- throw new IllegalArgumentException("Missing package name");
- }
- break;
- case "-S":
- final long sizeBytes = Long.parseLong(nextOptionData());
- if (sizeBytes <= 0) {
- throw new IllegalArgumentException("Size must be positive");
- }
- sessionParams.setSize(sizeBytes);
- break;
- case "--abi":
- sessionParams.abiOverride = checkAbiArgument(nextOptionData());
- break;
- case "--ephemeral":
- case "--instant":
- sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
- break;
- case "--full":
- sessionParams.setInstallAsInstantApp(false /*isInstantApp*/);
- break;
- case "--user":
- params.userId = UserHandle.parseUserArg(nextOptionData());
- break;
- case "--install-location":
- sessionParams.installLocation = Integer.parseInt(nextOptionData());
- break;
- case "--force-uuid":
- sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
- sessionParams.volumeUuid = nextOptionData();
- if ("internal".equals(sessionParams.volumeUuid)) {
- sessionParams.volumeUuid = null;
- }
- break;
- case "--force-sdk":
- sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
- break;
- default:
- throw new IllegalArgumentException("Unknown option " + opt);
- }
- }
- return params;
- }
-
- private int doCreateSession(SessionParams params, String installerPackageName, int userId)
- throws RemoteException {
- userId = translateUserId(userId, "runInstallCreate");
- if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_SYSTEM;
- params.installFlags |= PackageManager.INSTALL_ALL_USERS;
- }
-
- final int sessionId = mInstaller.createSession(params, installerPackageName, userId);
- return sessionId;
- }
-
- private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName,
- boolean logSuccess) throws RemoteException {
- if (STDIN_PATH.equals(inPath)) {
- inPath = null;
- } else if (inPath != null) {
- final File file = new File(inPath);
- if (file.isFile()) {
- sizeBytes = file.length();
- }
- }
-
- final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-
- PackageInstaller.Session session = null;
- InputStream in = null;
- OutputStream out = null;
- try {
- session = new PackageInstaller.Session(
- mInstaller.openSession(sessionId));
-
- if (inPath != null) {
- in = new FileInputStream(inPath);
- } else {
- in = new SizedInputStream(System.in, sizeBytes);
- }
- out = session.openWrite(splitName, 0, sizeBytes);
-
- int total = 0;
- byte[] buffer = new byte[1024 * 1024];
- int c;
- while ((c = in.read(buffer)) != -1) {
- total += c;
- out.write(buffer, 0, c);
-
- if (info.sizeBytes > 0) {
- final float fraction = ((float) c / (float) info.sizeBytes);
- session.addProgress(fraction);
- }
- }
- session.fsync(out);
-
- if (logSuccess) {
- System.out.println("Success: streamed " + total + " bytes");
- }
- return PackageInstaller.STATUS_SUCCESS;
- } catch (IOException e) {
- System.err.println("Error: failed to write; " + e.getMessage());
- return PackageInstaller.STATUS_FAILURE;
- } finally {
- IoUtils.closeQuietly(out);
- IoUtils.closeQuietly(in);
- IoUtils.closeQuietly(session);
- }
- }
-
- private Pair<String, Integer> doCommitSession(int sessionId, boolean logSuccess)
- throws RemoteException {
- PackageInstaller.Session session = null;
- try {
- session = new PackageInstaller.Session(
- mInstaller.openSession(sessionId));
-
- final LocalIntentReceiver receiver = new LocalIntentReceiver();
- session.commit(receiver.getIntentSender());
-
- final Intent result = receiver.getResult();
- final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_FAILURE);
- if (status == PackageInstaller.STATUS_SUCCESS) {
- if (logSuccess) {
- System.out.println("Success");
- }
- } else {
- System.err.println("Failure ["
- + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
- }
- return new Pair<>(result.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME), status);
- } finally {
- IoUtils.closeQuietly(session);
- }
- }
-
- private int doAbandonSession(int sessionId, boolean logSuccess) throws RemoteException {
- PackageInstaller.Session session = null;
- try {
- session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
- session.abandon();
- if (logSuccess) {
- System.out.println("Success");
- }
- return PackageInstaller.STATUS_SUCCESS;
- } finally {
- IoUtils.closeQuietly(session);
- }
- }
-
- class LocalPackageInstallObserver extends PackageInstallObserver {
- boolean finished;
- int result;
- String extraPermission;
- String extraPackage;
-
- @Override
- public void onPackageInstalled(String name, int status, String msg, Bundle extras) {
- synchronized (this) {
- finished = true;
- result = status;
- if (status == PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION) {
- extraPermission = extras.getString(
- PackageManager.EXTRA_FAILURE_EXISTING_PERMISSION);
- extraPackage = extras.getString(
- PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
- }
- notifyAll();
- }
- }
- }
-
- private static boolean isNumber(String s) {
- try {
- Integer.parseInt(s);
- } catch (NumberFormatException nfe) {
- return false;
- }
- return true;
- }
-
- static class ClearCacheObserver extends IPackageDataObserver.Stub {
- boolean finished;
- boolean result;
-
- @Override
- public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
- synchronized (this) {
- finished = true;
- result = succeeded;
- notifyAll();
- }
- }
-
- }
-
- static class ClearDataObserver extends IPackageDataObserver.Stub {
- boolean finished;
- boolean result;
-
- @Override
- public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
- synchronized (this) {
- finished = true;
- result = succeeded;
- notifyAll();
- }
- }
- }
-
- /**
- * Displays the package file for a package.
- * @param pckg
- */
- private int displayPackageFilePath(String pckg, int userId) {
- try {
- PackageInfo info = mPm.getPackageInfo(pckg, 0, userId);
- if (info != null && info.applicationInfo != null) {
- System.out.print("package:");
- System.out.println(info.applicationInfo.sourceDir);
- if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
- for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
- System.out.print("package:");
- System.out.println(splitSourceDir);
- }
- }
- return 0;
- }
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- }
- return 1;
- }
-
- private String nextOption() {
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String arg = mArgs[mNextArg];
- if (!arg.startsWith("-")) {
- return null;
- }
- mNextArg++;
- if (arg.equals("--")) {
- return null;
- }
- if (arg.length() > 1 && arg.charAt(1) != '-') {
- if (arg.length() > 2) {
- mCurArgData = arg.substring(2);
- return arg.substring(0, 2);
- } else {
- mCurArgData = null;
- return arg;
- }
- }
- mCurArgData = null;
- return arg;
- }
-
- private String nextOptionData() {
- if (mCurArgData != null) {
- return mCurArgData;
- }
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String data = mArgs[mNextArg];
- mNextArg++;
- return data;
- }
-
- private String nextArg() {
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String arg = mArgs[mNextArg];
- mNextArg++;
- return arg;
- }
-
- 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 [-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");
- System.err.println(" pm set-installer PACKAGE INSTALLER");
- System.err.println(" pm move-package PACKAGE [internal|UUID]");
- System.err.println(" pm move-primary-storage [internal|UUID]");
- System.err.println(" pm clear [--user USER_ID] PACKAGE");
- System.err.println(" pm enable [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm disable [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm default-state [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm set-user-restriction [--user USER_ID] RESTRICTION VALUE");
- System.err.println(" pm hide [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm grant [--user USER_ID] PACKAGE PERMISSION");
- System.err.println(" pm revoke [--user USER_ID] PACKAGE PERMISSION");
- System.err.println(" pm reset-permissions");
- System.err.println(" pm set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}");
- System.err.println(" pm get-app-link [--user USER_ID] PACKAGE");
- System.err.println(" pm set-install-location [0/auto] [1/internal] [2/external]");
- System.err.println(" pm get-install-location");
- System.err.println(" pm set-permission-enforced PERMISSION [true|false]");
- System.err.println(" pm trim-caches DESIRED_FREE_SPACE [internal|UUID]");
- System.err.println(" pm create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral] [--guest] USER_NAME");
- System.err.println(" pm remove-user USER_ID");
- System.err.println(" pm get-max-users");
- System.err.println("");
- System.err.println("NOTE: 'pm list' commands have moved! Run 'adb shell cmd package'");
- System.err.println(" to display the new commands.");
- System.err.println("");
- System.err.println("pm path: print the path to the .apk of the given PACKAGE.");
- System.err.println("");
- System.err.println("pm dump: print system state associated with the given PACKAGE.");
- System.err.println("");
- 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: allow replacement of existing application");
- System.err.println(" -t: allow test packages");
- 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 (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");
- System.err.println(" -S: size in bytes of package, required for stdin");
- System.err.println("");
- System.err.println("pm install-commit: perform install of fully staged session");
- System.err.println("pm install-abandon: abandon session");
- System.err.println("");
- System.err.println("pm set-installer: set installer package name");
- System.err.println("");
- System.err.println("pm uninstall: removes a package from the system. Options:");
- System.err.println(" -k: keep the data and cache directories around after package removal.");
- System.err.println("");
- System.err.println("pm clear: deletes all data associated with a package.");
- System.err.println("");
- System.err.println("pm enable, disable, disable-user, disable-until-used, default-state:");
- System.err.println(" these commands change the enabled state of a given package or");
- System.err.println(" component (written as \"package/class\").");
- System.err.println("");
- System.err.println("pm grant, revoke: these commands either grant or revoke permissions");
- System.err.println(" to apps. The permissions must be declared as used in the app's");
- System.err.println(" manifest, be runtime permissions (protection level dangerous),");
- System.err.println(" and the app targeting SDK greater than Lollipop MR1.");
- System.err.println("");
- System.err.println("pm reset-permissions: revert all runtime permissions to their default state.");
- System.err.println("");
- System.err.println("pm get-install-location: returns the current install location.");
- System.err.println(" 0 [auto]: Let system decide the best location");
- System.err.println(" 1 [internal]: Install on internal device storage");
- System.err.println(" 2 [external]: Install on external media");
- System.err.println("");
- System.err.println("pm set-install-location: changes the default install location.");
- System.err.println(" NOTE: this is only intended for debugging; using this can cause");
- System.err.println(" applications to break and other undersireable behavior.");
- System.err.println(" 0 [auto]: Let system decide the best location");
- System.err.println(" 1 [internal]: Install on internal device storage");
- System.err.println(" 2 [external]: Install on external media");
- System.err.println("");
- System.err.println("pm trim-caches: trim cache files to reach the given free space.");
- System.err.println("");
- System.err.println("pm create-user: create a new user with the given USER_NAME,");
- System.err.println(" printing the new user identifier of the user.");
- System.err.println("");
- System.err.println("pm remove-user: remove the user with the given USER_IDENTIFIER,");
- System.err.println(" deleting all data associated with that user");
- System.err.println("");
- return 1;
- }
-}
diff --git a/com/android/datetimepicker/AccessibleLinearLayout.java b/com/android/datetimepicker/AccessibleLinearLayout.java
index 629f8564..a67d8095 100644
--- a/com/android/datetimepicker/AccessibleLinearLayout.java
+++ b/com/android/datetimepicker/AccessibleLinearLayout.java
@@ -25,7 +25,10 @@ import android.widget.LinearLayout;
/**
* Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
*/
+@Deprecated
public class AccessibleLinearLayout extends LinearLayout {
public AccessibleLinearLayout(Context context, AttributeSet attrs) {
diff --git a/com/android/datetimepicker/AccessibleTextView.java b/com/android/datetimepicker/AccessibleTextView.java
index 98fa7442..9a2ecbb2 100644
--- a/com/android/datetimepicker/AccessibleTextView.java
+++ b/com/android/datetimepicker/AccessibleTextView.java
@@ -25,7 +25,10 @@ import android.widget.TextView;
/**
* Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
*/
+@Deprecated
public class AccessibleTextView extends TextView {
public AccessibleTextView(Context context, AttributeSet attrs) {
diff --git a/com/android/datetimepicker/HapticFeedbackController.java b/com/android/datetimepicker/HapticFeedbackController.java
index b9be63f2..96d3a24c 100644
--- a/com/android/datetimepicker/HapticFeedbackController.java
+++ b/com/android/datetimepicker/HapticFeedbackController.java
@@ -10,7 +10,10 @@ import android.provider.Settings;
/**
* A simple utility class to handle haptic feedback.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
*/
+@Deprecated
public class HapticFeedbackController {
private static final int VIBRATE_DELAY_MS = 125;
private static final int VIBRATE_LENGTH_MS = 5;
diff --git a/com/android/datetimepicker/Utils.java b/com/android/datetimepicker/Utils.java
index 4a3110c4..6c0adbef 100644
--- a/com/android/datetimepicker/Utils.java
+++ b/com/android/datetimepicker/Utils.java
@@ -28,7 +28,10 @@ import java.util.Calendar;
/**
* Utility helper functions for time and date pickers.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
*/
+@Deprecated
public class Utils {
public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
diff --git a/com/android/datetimepicker/date/AccessibleDateAnimator.java b/com/android/datetimepicker/date/AccessibleDateAnimator.java
index fc022cda..4e65aead 100644
--- a/com/android/datetimepicker/date/AccessibleDateAnimator.java
+++ b/com/android/datetimepicker/date/AccessibleDateAnimator.java
@@ -22,7 +22,7 @@ import android.util.AttributeSet;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ViewAnimator;
-public class AccessibleDateAnimator extends ViewAnimator {
+class AccessibleDateAnimator extends ViewAnimator {
private long mDateMillis;
public AccessibleDateAnimator(Context context, AttributeSet attrs) {
diff --git a/com/android/datetimepicker/date/DatePickerController.java b/com/android/datetimepicker/date/DatePickerController.java
index 42989ddc..b027d338 100644
--- a/com/android/datetimepicker/date/DatePickerController.java
+++ b/com/android/datetimepicker/date/DatePickerController.java
@@ -24,7 +24,7 @@ import java.util.Calendar;
/**
* Controller class to communicate among the various components of the date picker dialog.
*/
-public interface DatePickerController {
+interface DatePickerController {
void onYearSelected(int year);
diff --git a/com/android/datetimepicker/date/DatePickerDialog.java b/com/android/datetimepicker/date/DatePickerDialog.java
index 994fdf2e..9b26b48a 100644
--- a/com/android/datetimepicker/date/DatePickerDialog.java
+++ b/com/android/datetimepicker/date/DatePickerDialog.java
@@ -48,7 +48,10 @@ import java.util.Locale;
/**
* Dialog allowing users to select a date.
+ *
+ * @deprecated Use {@link android.app.DatePickerDialog}.
*/
+@Deprecated
public class DatePickerDialog extends DialogFragment implements
OnClickListener, DatePickerController {
diff --git a/com/android/datetimepicker/date/DayPickerView.java b/com/android/datetimepicker/date/DayPickerView.java
index 47a2aa72..9f815376 100644
--- a/com/android/datetimepicker/date/DayPickerView.java
+++ b/com/android/datetimepicker/date/DayPickerView.java
@@ -42,7 +42,7 @@ import java.util.Locale;
/**
* This displays a list of months in a calendar format with selectable days.
*/
-public abstract class DayPickerView extends ListView implements OnScrollListener,
+abstract class DayPickerView extends ListView implements OnScrollListener,
OnDateChangedListener {
private static final String TAG = "MonthFragment";
diff --git a/com/android/datetimepicker/date/MonthAdapter.java b/com/android/datetimepicker/date/MonthAdapter.java
index 3ed88b02..0f95e555 100644
--- a/com/android/datetimepicker/date/MonthAdapter.java
+++ b/com/android/datetimepicker/date/MonthAdapter.java
@@ -32,7 +32,7 @@ import java.util.HashMap;
/**
* An adapter for a list of {@link MonthView} items.
*/
-public abstract class MonthAdapter extends BaseAdapter implements OnDayClickListener {
+abstract class MonthAdapter extends BaseAdapter implements OnDayClickListener {
private static final String TAG = "SimpleMonthAdapter";
diff --git a/com/android/datetimepicker/date/MonthView.java b/com/android/datetimepicker/date/MonthView.java
index 00711f3e..63d073c6 100644
--- a/com/android/datetimepicker/date/MonthView.java
+++ b/com/android/datetimepicker/date/MonthView.java
@@ -52,7 +52,7 @@ import java.util.Locale;
* A calendar-like view displaying a specified month and the appropriate selectable day numbers
* within the specified month.
*/
-public abstract class MonthView extends View {
+abstract class MonthView extends View {
private static final String TAG = "MonthView";
/**
diff --git a/com/android/datetimepicker/date/SimpleDayPickerView.java b/com/android/datetimepicker/date/SimpleDayPickerView.java
index 658c8a28..c20c754c 100644
--- a/com/android/datetimepicker/date/SimpleDayPickerView.java
+++ b/com/android/datetimepicker/date/SimpleDayPickerView.java
@@ -22,7 +22,7 @@ import android.util.AttributeSet;
/**
* A DayPickerView customized for {@link SimpleMonthAdapter}
*/
-public class SimpleDayPickerView extends DayPickerView {
+class SimpleDayPickerView extends DayPickerView {
public SimpleDayPickerView(Context context, AttributeSet attrs) {
super(context, attrs);
diff --git a/com/android/datetimepicker/date/SimpleMonthAdapter.java b/com/android/datetimepicker/date/SimpleMonthAdapter.java
index 0c939fec..394abc20 100644
--- a/com/android/datetimepicker/date/SimpleMonthAdapter.java
+++ b/com/android/datetimepicker/date/SimpleMonthAdapter.java
@@ -21,7 +21,7 @@ import android.content.Context;
/**
* An adapter for a list of {@link SimpleMonthView} items.
*/
-public class SimpleMonthAdapter extends MonthAdapter {
+class SimpleMonthAdapter extends MonthAdapter {
public SimpleMonthAdapter(Context context, DatePickerController controller) {
super(context, controller);
diff --git a/com/android/datetimepicker/date/SimpleMonthView.java b/com/android/datetimepicker/date/SimpleMonthView.java
index b416a45f..bb392eb9 100644
--- a/com/android/datetimepicker/date/SimpleMonthView.java
+++ b/com/android/datetimepicker/date/SimpleMonthView.java
@@ -21,7 +21,7 @@ import android.graphics.Canvas;
import java.util.Calendar;
-public class SimpleMonthView extends MonthView {
+class SimpleMonthView extends MonthView {
public SimpleMonthView(Context context) {
super(context);
diff --git a/com/android/datetimepicker/date/TextViewWithCircularIndicator.java b/com/android/datetimepicker/date/TextViewWithCircularIndicator.java
index ad78746a..e4d69e14 100644
--- a/com/android/datetimepicker/date/TextViewWithCircularIndicator.java
+++ b/com/android/datetimepicker/date/TextViewWithCircularIndicator.java
@@ -29,7 +29,10 @@ import com.android.datetimepicker.R;
/**
* A text view which, when pressed or activated, displays a blue circle around the text.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
*/
+@Deprecated
public class TextViewWithCircularIndicator extends TextView {
private static final int SELECTED_CIRCLE_ALPHA = 60;
diff --git a/com/android/datetimepicker/date/YearPickerView.java b/com/android/datetimepicker/date/YearPickerView.java
index ae14eb50..d058b36a 100644
--- a/com/android/datetimepicker/date/YearPickerView.java
+++ b/com/android/datetimepicker/date/YearPickerView.java
@@ -37,7 +37,7 @@ import java.util.List;
/**
* Displays a selectable list of years.
*/
-public class YearPickerView extends ListView implements OnItemClickListener, OnDateChangedListener {
+class YearPickerView extends ListView implements OnItemClickListener, OnDateChangedListener {
private static final String TAG = "YearPickerView";
private final DatePickerController mController;
diff --git a/com/android/datetimepicker/time/AmPmCirclesView.java b/com/android/datetimepicker/time/AmPmCirclesView.java
index 902abd95..bafa51c5 100644
--- a/com/android/datetimepicker/time/AmPmCirclesView.java
+++ b/com/android/datetimepicker/time/AmPmCirclesView.java
@@ -33,7 +33,7 @@ import java.text.DateFormatSymbols;
/**
* Draw the two smaller AM and PM circles next to where the larger circle will be.
*/
-public class AmPmCirclesView extends View {
+class AmPmCirclesView extends View {
private static final String TAG = "AmPmCirclesView";
// Alpha level for selected circle.
diff --git a/com/android/datetimepicker/time/CircleView.java b/com/android/datetimepicker/time/CircleView.java
index 1dd4eeab..0d6766fb 100644
--- a/com/android/datetimepicker/time/CircleView.java
+++ b/com/android/datetimepicker/time/CircleView.java
@@ -28,7 +28,7 @@ import com.android.datetimepicker.R;
/**
* Draws a simple white circle on which the numbers will be drawn.
*/
-public class CircleView extends View {
+class CircleView extends View {
private static final String TAG = "CircleView";
private final Paint mPaint = new Paint();
diff --git a/com/android/datetimepicker/time/RadialPickerLayout.java b/com/android/datetimepicker/time/RadialPickerLayout.java
index 1d449077..6a4adeac 100644
--- a/com/android/datetimepicker/time/RadialPickerLayout.java
+++ b/com/android/datetimepicker/time/RadialPickerLayout.java
@@ -44,6 +44,8 @@ import com.android.datetimepicker.R;
* The primary layout to hold the circular picker, and the am/pm buttons. This view well measure
* itself to end up as a square. It also handles touches to be passed in to views that need to know
* when they'd been touched.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
*/
public class RadialPickerLayout extends FrameLayout implements OnTouchListener {
private static final String TAG = "RadialPickerLayout";
diff --git a/com/android/datetimepicker/time/RadialSelectorView.java b/com/android/datetimepicker/time/RadialSelectorView.java
index 0339dcd2..49069914 100644
--- a/com/android/datetimepicker/time/RadialSelectorView.java
+++ b/com/android/datetimepicker/time/RadialSelectorView.java
@@ -35,7 +35,7 @@ import com.android.datetimepicker.Utils;
* View to show what number is selected. This will draw a blue circle over the number, with a blue
* line coming from the center of the main circle to the edge of the blue selection.
*/
-public class RadialSelectorView extends View {
+class RadialSelectorView extends View {
private static final String TAG = "RadialSelectorView";
// Alpha level for selected circle.
diff --git a/com/android/datetimepicker/time/RadialTextsView.java b/com/android/datetimepicker/time/RadialTextsView.java
index 684e8f50..0b69d062 100644
--- a/com/android/datetimepicker/time/RadialTextsView.java
+++ b/com/android/datetimepicker/time/RadialTextsView.java
@@ -35,7 +35,7 @@ import com.android.datetimepicker.R;
/**
* A view to show a series of numbers in a circular pattern.
*/
-public class RadialTextsView extends View {
+class RadialTextsView extends View {
private final static String TAG = "RadialTextsView";
private final Paint mPaint = new Paint();
diff --git a/com/android/datetimepicker/time/TimePickerDialog.java b/com/android/datetimepicker/time/TimePickerDialog.java
index c7661ad5..0dd13c9c 100644
--- a/com/android/datetimepicker/time/TimePickerDialog.java
+++ b/com/android/datetimepicker/time/TimePickerDialog.java
@@ -46,7 +46,10 @@ import java.util.Locale;
/**
* Dialog to set a time.
+ *
+ * @deprecated Use {@link android.app.TimePickerDialog}.
*/
+@Deprecated
public class TimePickerDialog extends DialogFragment implements OnValueSelectedListener{
private static final String TAG = "TimePickerDialog";
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/ims/ImsManager.java b/com/android/ims/ImsManager.java
index 813118ba..e0a966a7 100644
--- a/com/android/ims/ImsManager.java
+++ b/com/android/ims/ImsManager.java
@@ -43,6 +43,7 @@ import android.util.Log;
import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsConfig;
import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMMTelFeature;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsRegistrationListener;
import com.android.ims.internal.IImsServiceController;
@@ -281,22 +282,28 @@ public class ImsManager {
}
/**
- * Returns the user configuration of Enhanced 4G LTE Mode setting for slot. If not set, it
- * returns true as default value.
+ * Returns the user configuration of Enhanced 4G LTE Mode setting for slot. If the option is
+ * not editable ({@link CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL} is false), or
+ * the setting is not initialized, this method will return default value specified by
+ * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
+ *
+ * Note that even if the setting was set, it may no longer be editable. If this is the case we
+ * return the default value.
*/
public boolean isEnhanced4gLteModeSettingEnabledByUser() {
- // If user can't edit Enhanced 4G LTE Mode, it assumes Enhanced 4G LTE Mode is always true.
- // If user changes SIM from editable mode to uneditable mode, need to return true.
- if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) {
- return true;
- }
-
int setting = SubscriptionManager.getIntegerSubscriptionProperty(
getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
SUB_PROPERTY_NOT_INITIALIZED, mContext);
+ boolean onByDefault = getBooleanCarrierConfig(
+ CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL);
- // If it's never set, by default we return true.
- return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1);
+ // If Enhanced 4G LTE Mode is uneditable or not initialized, we use the default value
+ if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)
+ || setting == SUB_PROPERTY_NOT_INITIALIZED) {
+ return onByDefault;
+ } else {
+ return (setting == ImsConfig.FeatureValueConstants.ON);
+ }
}
/**
@@ -315,21 +322,26 @@ public class ImsManager {
}
/**
- * Change persistent Enhanced 4G LTE Mode setting. If the the option is not editable
+ * Change persistent Enhanced 4G LTE Mode setting. If the option is not editable
* ({@link CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL} is false), this method will
- * always set the setting to true.
+ * set the setting to the default value specified by
+ * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
*
*/
public void setEnhanced4gLteModeSetting(boolean enabled) {
- // If false, we must always keep advanced 4G mode set to true.
- enabled = getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)
- ? enabled : true;
+ // If editable=false, we must keep default advanced 4G mode.
+ if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) {
+ enabled = getBooleanCarrierConfig(
+ CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL);
+ }
int prevSetting = SubscriptionManager.getIntegerSubscriptionProperty(
getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
SUB_PROPERTY_NOT_INITIALIZED, mContext);
- if (prevSetting != (enabled ? 1 : 0)) {
+ if (prevSetting != (enabled ?
+ ImsConfig.FeatureValueConstants.ON :
+ ImsConfig.FeatureValueConstants.OFF)) {
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(enabled));
if (isNonTtyOrTtyOnVolteEnabled()) {
@@ -566,7 +578,8 @@ public class ImsManager {
SUB_PROPERTY_NOT_INITIALIZED, mContext);
// If it's never set, by default we return true.
- return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1);
+ return (setting == SUB_PROPERTY_NOT_INITIALIZED
+ || setting == ImsConfig.FeatureValueConstants.ON);
}
/**
@@ -672,7 +685,7 @@ public class ImsManager {
return getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL);
} else {
- return setting == 1;
+ return setting == ImsConfig.FeatureValueConstants.ON;
}
}
@@ -957,7 +970,7 @@ public class ImsManager {
return getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL);
} else {
- return (setting == 1);
+ return setting == ImsConfig.FeatureValueConstants.ON;
}
}
@@ -1982,8 +1995,8 @@ public class ImsManager {
serviceProxy.setStatusCallback(() -> mStatusCallbacks.forEach(
ImsServiceProxy.INotifyStatusChanged::notifyStatusChanged));
// Returns null if the service is not available.
- IImsServiceController b = tm.getImsServiceControllerAndListen(mPhoneId,
- ImsFeature.MMTEL, serviceProxy.getListener());
+ IImsMMTelFeature b = tm.getImsMMTelFeatureAndListen(mPhoneId,
+ serviceProxy.getListener());
if (b != null) {
serviceProxy.setBinder(b.asBinder());
// Trigger the cache to be updated for feature status.
@@ -2405,7 +2418,9 @@ public class ImsManager {
public void factoryReset() {
// Set VoLTE to default
SubscriptionManager.setSubscriptionProperty(getSubId(),
- SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(true));
+ SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
+ booleanToPropertyString(getBooleanCarrierConfig(
+ CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL)));
// Set VoWiFi to default
SubscriptionManager.setSubscriptionProperty(getSubId(),
diff --git a/com/android/ims/ImsServiceProxy.java b/com/android/ims/ImsServiceProxy.java
index 8c51202f..f3489194 100644
--- a/com/android/ims/ImsServiceProxy.java
+++ b/com/android/ims/ImsServiceProxy.java
@@ -27,10 +27,10 @@ import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsCallSessionListener;
import com.android.ims.internal.IImsConfig;
import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMMTelFeature;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.ims.internal.IImsUt;
/**
@@ -57,8 +57,8 @@ public class ImsServiceProxy {
void notifyStatusChanged();
}
- private final IImsServiceFeatureListener mListenerBinder =
- new IImsServiceFeatureListener.Stub() {
+ private final IImsServiceFeatureCallback mListenerBinder =
+ new IImsServiceFeatureCallback.Stub() {
@Override
public void imsFeatureCreated(int slotId, int feature) throws RemoteException {
@@ -108,7 +108,7 @@ public class ImsServiceProxy {
this(slotId, null, featureType);
}
- public IImsServiceFeatureListener getListener() {
+ public IImsServiceFeatureCallback getListener() {
return mListenerBinder;
}
@@ -120,8 +120,7 @@ public class ImsServiceProxy {
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).startSession(mSlotId, mSupportedFeature,
- incomingCallIntent, listener);
+ return getServiceInterface(mBinder).startSession(incomingCallIntent, listener);
}
}
@@ -130,7 +129,7 @@ public class ImsServiceProxy {
// Only check to make sure the binder connection still exists. This method should
// still be able to be called when the state is STATE_NOT_AVAILABLE.
checkBinderConnection();
- getServiceInterface(mBinder).endSession(mSlotId, mSupportedFeature, sessionId);
+ getServiceInterface(mBinder).endSession(sessionId);
}
}
@@ -138,15 +137,14 @@ public class ImsServiceProxy {
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature,
- callServiceType, callType);
+ return getServiceInterface(mBinder).isConnected(callServiceType, callType);
}
}
public boolean isOpened() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature);
+ return getServiceInterface(mBinder).isOpened();
}
}
@@ -154,8 +152,7 @@ public class ImsServiceProxy {
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- getServiceInterface(mBinder).addRegistrationListener(mSlotId, mSupportedFeature,
- listener);
+ getServiceInterface(mBinder).addRegistrationListener(listener);
}
}
@@ -163,8 +160,7 @@ public class ImsServiceProxy {
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- getServiceInterface(mBinder).removeRegistrationListener(mSlotId, mSupportedFeature,
- listener);
+ getServiceInterface(mBinder).removeRegistrationListener(listener);
}
}
@@ -172,8 +168,8 @@ public class ImsServiceProxy {
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).createCallProfile(mSlotId, mSupportedFeature,
- sessionId, callServiceType, callType);
+ return getServiceInterface(mBinder).createCallProfile(sessionId, callServiceType,
+ callType);
}
}
@@ -181,8 +177,7 @@ public class ImsServiceProxy {
IImsCallSessionListener listener) throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).createCallSession(mSlotId, mSupportedFeature,
- sessionId, profile, listener);
+ return getServiceInterface(mBinder).createCallSession(sessionId, profile, listener);
}
}
@@ -190,43 +185,42 @@ public class ImsServiceProxy {
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).getPendingCallSession(mSlotId, mSupportedFeature,
- sessionId, callId);
+ return getServiceInterface(mBinder).getPendingCallSession(sessionId, callId);
}
}
public IImsUt getUtInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature);
+ return getServiceInterface(mBinder).getUtInterface();
}
}
public IImsConfig getConfigInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature);
+ return getServiceInterface(mBinder).getConfigInterface();
}
}
public void turnOnIms() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature);
+ getServiceInterface(mBinder).turnOnIms();
}
}
public void turnOffIms() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature);
+ getServiceInterface(mBinder).turnOffIms();
}
}
public IImsEcbm getEcbmInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature);
+ return getServiceInterface(mBinder).getEcbmInterface();
}
}
@@ -234,16 +228,14 @@ public class ImsServiceProxy {
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, uiTtyMode,
- onComplete);
+ getServiceInterface(mBinder).setUiTTYMode(uiTtyMode, onComplete);
}
}
public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).getMultiEndpointInterface(mSlotId,
- mSupportedFeature);
+ return getServiceInterface(mBinder).getMultiEndpointInterface();
}
}
@@ -277,7 +269,7 @@ public class ImsServiceProxy {
private Integer retrieveFeatureStatus() {
if (mBinder != null) {
try {
- return getServiceInterface(mBinder).getFeatureStatus(mSlotId, mSupportedFeature);
+ return getServiceInterface(mBinder).getFeatureStatus();
} catch (RemoteException e) {
// Status check failed, don't update cache
}
@@ -318,8 +310,8 @@ public class ImsServiceProxy {
}
}
- private IImsServiceController getServiceInterface(IBinder b) {
- return IImsServiceController.Stub.asInterface(b);
+ private IImsMMTelFeature getServiceInterface(IBinder b) {
+ return IImsMMTelFeature.Stub.asInterface(b);
}
protected void checkBinderConnection() throws RemoteException {
diff --git a/com/android/internal/app/procstats/ProcessState.java b/com/android/internal/app/procstats/ProcessState.java
index 7519fce4..fbdf17d8 100644
--- a/com/android/internal/app/procstats/ProcessState.java
+++ b/com/android/internal/app/procstats/ProcessState.java
@@ -102,6 +102,7 @@ public final class ProcessState {
STATE_LAST_ACTIVITY, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
STATE_CACHED_ACTIVITY_CLIENT, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_RECENT
STATE_CACHED_EMPTY, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
diff --git a/com/android/internal/content/NativeLibraryHelper.java b/com/android/internal/content/NativeLibraryHelper.java
index 83b7d2f9..a1e6fd8e 100644
--- a/com/android/internal/content/NativeLibraryHelper.java
+++ b/com/android/internal/content/NativeLibraryHelper.java
@@ -43,6 +43,7 @@ import dalvik.system.VMRuntime;
import java.io.Closeable;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.util.List;
@@ -118,6 +119,17 @@ public class NativeLibraryHelper {
return new Handle(apkHandles, multiArch, extractNativeLibs, debuggable);
}
+ public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException {
+ final long[] apkHandles = new long[1];
+ final String path = lite.baseCodePath;
+ apkHandles[0] = nativeOpenApkFd(fd, path);
+ if (apkHandles[0] == 0) {
+ throw new IOException("Unable to open APK " + path + " from fd " + fd);
+ }
+
+ return new Handle(apkHandles, lite.multiArch, lite.extractNativeLibs, lite.debuggable);
+ }
+
Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs,
boolean debuggable) {
this.apkHandles = apkHandles;
@@ -152,6 +164,7 @@ public class NativeLibraryHelper {
}
private static native long nativeOpenApk(String path);
+ private static native long nativeOpenApkFd(FileDescriptor fd, String debugPath);
private static native void nativeClose(long handle);
private static native long nativeSumNativeBinaries(long handle, String cpuAbi,
diff --git a/com/android/internal/content/PackageHelper.java b/com/android/internal/content/PackageHelper.java
index 59a7995a..e765ab1e 100644
--- a/com/android/internal/content/PackageHelper.java
+++ b/com/android/internal/content/PackageHelper.java
@@ -42,6 +42,7 @@ import com.android.internal.annotations.VisibleForTesting;
import libcore.io.IoUtils;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Objects;
import java.util.UUID;
@@ -383,9 +384,15 @@ public class PackageHelper {
public static long calculateInstalledSize(PackageLite pkg, String abiOverride)
throws IOException {
+ return calculateInstalledSize(pkg, abiOverride, null);
+ }
+
+ public static long calculateInstalledSize(PackageLite pkg, String abiOverride,
+ FileDescriptor fd) throws IOException {
NativeLibraryHelper.Handle handle = null;
try {
- handle = NativeLibraryHelper.Handle.create(pkg);
+ handle = fd != null ? NativeLibraryHelper.Handle.createFd(pkg, fd)
+ : NativeLibraryHelper.Handle.create(pkg);
return calculateInstalledSize(pkg, handle, abiOverride);
} finally {
IoUtils.closeQuietly(handle);
diff --git a/com/android/internal/content/ReferrerIntent.java b/com/android/internal/content/ReferrerIntent.java
index 8d9a1cf9..76dcc9bb 100644
--- a/com/android/internal/content/ReferrerIntent.java
+++ b/com/android/internal/content/ReferrerIntent.java
@@ -19,6 +19,8 @@ package com.android.internal.content;
import android.content.Intent;
import android.os.Parcel;
+import java.util.Objects;
+
/**
* Subclass of Intent that also contains referrer (as a package name) information.
*/
@@ -48,4 +50,21 @@ public class ReferrerIntent extends Intent {
return new ReferrerIntent[size];
}
};
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof ReferrerIntent)) {
+ return false;
+ }
+ final ReferrerIntent other = (ReferrerIntent) obj;
+ return filterEquals(other) && Objects.equals(mReferrer, other.mReferrer);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + filterHashCode();
+ result = 31 * result + Objects.hashCode(mReferrer);
+ return result;
+ }
}
diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java
index f2483c0a..56d0bb22 100644
--- a/com/android/internal/os/BatteryStatsImpl.java
+++ b/com/android/internal/os/BatteryStatsImpl.java
@@ -120,7 +120,7 @@ public class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 168 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 169 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -599,6 +599,8 @@ public class BatteryStatsImpl extends BatteryStats {
private LongSamplingCounter mDischargeScreenOffCounter;
private LongSamplingCounter mDischargeScreenDozeCounter;
private LongSamplingCounter mDischargeCounter;
+ private LongSamplingCounter mDischargeLightDozeCounter;
+ private LongSamplingCounter mDischargeDeepDozeCounter;
static final int MAX_LEVEL_STEPS = 200;
@@ -697,6 +699,16 @@ public class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public long getUahDischargeLightDoze(int which) {
+ return mDischargeLightDozeCounter.getCountLocked(which);
+ }
+
+ @Override
+ public long getUahDischargeDeepDoze(int which) {
+ return mDischargeDeepDozeCounter.getCountLocked(which);
+ }
+
+ @Override
public int getEstimatedBatteryCapacity() {
return mEstimatedBatteryCapacity;
}
@@ -6008,6 +6020,11 @@ public class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public Timer getMulticastWakelockStats() {
+ return mWifiMulticastTimer;
+ }
+
+ @Override
public ArrayMap<String, ? extends BatteryStats.Timer> getSyncStats() {
return mSyncStats.getMap();
}
@@ -9085,6 +9102,8 @@ public class BatteryStatsImpl extends BatteryStats {
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase);
mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
+ mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
+ mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
mOnBattery = mOnBatteryInternal = false;
long uptime = mClocks.uptimeMillis() * 1000;
@@ -9664,6 +9683,8 @@ public class BatteryStatsImpl extends BatteryStats {
mChargeStepTracker.init();
mDischargeScreenOffCounter.reset(false);
mDischargeScreenDozeCounter.reset(false);
+ mDischargeLightDozeCounter.reset(false);
+ mDischargeDeepDozeCounter.reset(false);
mDischargeCounter.reset(false);
}
@@ -11263,6 +11284,11 @@ public class BatteryStatsImpl extends BatteryStats {
if (isScreenDoze(mScreenState)) {
mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
}
+ if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
+ mDischargeLightDozeCounter.addCountLocked(chargeDiff);
+ } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) {
+ mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
+ }
}
mHistoryCur.batteryChargeUAh = chargeUAh;
setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);
@@ -11308,6 +11334,11 @@ public class BatteryStatsImpl extends BatteryStats {
if (isScreenDoze(mScreenState)) {
mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
}
+ if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
+ mDischargeLightDozeCounter.addCountLocked(chargeDiff);
+ } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) {
+ mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
+ }
}
mHistoryCur.batteryChargeUAh = chargeUAh;
changed = true;
@@ -12069,6 +12100,8 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeCounter.readSummaryFromParcelLocked(in);
mDischargeScreenOffCounter.readSummaryFromParcelLocked(in);
mDischargeScreenDozeCounter.readSummaryFromParcelLocked(in);
+ mDischargeLightDozeCounter.readSummaryFromParcelLocked(in);
+ mDischargeDeepDozeCounter.readSummaryFromParcelLocked(in);
int NPKG = in.readInt();
if (NPKG > 0) {
mDailyPackageChanges = new ArrayList<>(NPKG);
@@ -12493,6 +12526,8 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeCounter.writeSummaryFromParcelLocked(out);
mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out);
mDischargeScreenDozeCounter.writeSummaryFromParcelLocked(out);
+ mDischargeLightDozeCounter.writeSummaryFromParcelLocked(out);
+ mDischargeDeepDozeCounter.writeSummaryFromParcelLocked(out);
if (mDailyPackageChanges != null) {
final int NPKG = mDailyPackageChanges.size();
out.writeInt(NPKG);
@@ -13044,6 +13079,8 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, in);
mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
mLastWriteTime = in.readLong();
mRpmStats.clear();
@@ -13230,6 +13267,8 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeCounter.writeToParcel(out);
mDischargeScreenOffCounter.writeToParcel(out);
mDischargeScreenDozeCounter.writeToParcel(out);
+ mDischargeLightDozeCounter.writeToParcel(out);
+ mDischargeDeepDozeCounter.writeToParcel(out);
out.writeLong(mLastWriteTime);
out.writeInt(mRpmStats.size());
diff --git a/com/android/internal/policy/DecorView.java b/com/android/internal/policy/DecorView.java
index 85251d4b..5fddfba6 100644
--- a/com/android/internal/policy/DecorView.java
+++ b/com/android/internal/policy/DecorView.java
@@ -431,7 +431,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
}
}
- return super.dispatchKeyEvent(event);
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
+
+ return (getViewRootImpl() != null) && getViewRootImpl().dispatchKeyFallbackEvent(event);
}
public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
diff --git a/com/android/internal/telephony/CarrierIdentifier.java b/com/android/internal/telephony/CarrierIdentifier.java
new file mode 100644
index 00000000..e8be159b
--- /dev/null
+++ b/com/android/internal/telephony/CarrierIdentifier.java
@@ -0,0 +1,569 @@
+/*
+ * 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.internal.telephony;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.provider.Telephony;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.telephony.uicc.IccRecords;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static android.provider.Telephony.CarrierIdentification;
+
+/**
+ * CarrierIdentifier identifies the subscription carrier and returns a canonical carrier Id
+ * and a user friendly carrier name. CarrierIdentifier reads subscription info and check against
+ * all carrier matching rules stored in CarrierIdProvider. It is msim aware, each phone has a
+ * dedicated CarrierIdentifier.
+ */
+public class CarrierIdentifier extends Handler {
+ private static final String LOG_TAG = CarrierIdentifier.class.getSimpleName();
+ private static final boolean DBG = true;
+ private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
+
+ // events to trigger carrier identification
+ private static final int SIM_LOAD_EVENT = 1;
+ private static final int SIM_ABSENT_EVENT = 2;
+ private static final int SPN_OVERRIDE_EVENT = 3;
+ private static final int ICC_CHANGED_EVENT = 4;
+ private static final int PREFER_APN_UPDATE_EVENT = 5;
+ private static final int CARRIER_ID_DB_UPDATE_EVENT = 6;
+
+ private static final Uri CONTENT_URL_PREFER_APN = Uri.withAppendedPath(
+ Telephony.Carriers.CONTENT_URI, "preferapn");
+ private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_";
+ private static final int INVALID_CARRIER_ID = -1;
+
+ // cached matching rules based mccmnc to speed up resolution
+ private List<CarrierMatchingRule> mCarrierMatchingRulesOnMccMnc = new ArrayList<>();
+ // cached carrier Id
+ private int mCarrierId = INVALID_CARRIER_ID;
+ // cached carrier name
+ private String mCarrierName;
+ // cached preferapn name
+ private String mPreferApn;
+ // cached service provider name. telephonyManager API returns empty string as default value.
+ // some carriers need to target devices with Empty SPN. In that case, carrier matching rule
+ // should specify "" spn explicitly.
+ private String mSpn = "";
+
+ private Context mContext;
+ private Phone mPhone;
+ private IccRecords mIccRecords;
+ private final LocalLog mCarrierIdLocalLog = new LocalLog(20);
+ private final TelephonyManager mTelephonyMgr;
+ private final SubscriptionsChangedListener mOnSubscriptionsChangedListener =
+ new SubscriptionsChangedListener();
+ private final SharedPreferenceChangedListener mSharedPrefListener =
+ new SharedPreferenceChangedListener();
+
+ private final ContentObserver mContentObserver = new ContentObserver(this) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ logd("onChange URI: " + uri);
+ if (CONTENT_URL_PREFER_APN.equals(uri.getLastPathSegment())) {
+ sendEmptyMessage(PREFER_APN_UPDATE_EVENT);
+ } else {
+ sendEmptyMessage(CARRIER_ID_DB_UPDATE_EVENT);
+ }
+ }
+ };
+
+ private class SubscriptionsChangedListener
+ extends SubscriptionManager.OnSubscriptionsChangedListener {
+ final AtomicInteger mPreviousSubId =
+ new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ /**
+ * Callback invoked when there is any change to any SubscriptionInfo. Typically
+ * this method would invoke {@link SubscriptionManager#getActiveSubscriptionInfoList}
+ */
+ @Override
+ public void onSubscriptionsChanged() {
+ int subId = mPhone.getSubId();
+ if (mPreviousSubId.getAndSet(subId) != subId) {
+ if (DBG) {
+ logd("SubscriptionListener.onSubscriptionInfoChanged subId: "
+ + mPreviousSubId);
+ }
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
+ sendEmptyMessage(SIM_LOAD_EVENT);
+ } else {
+ sendEmptyMessage(SIM_ABSENT_EVENT);
+ }
+ }
+ }
+ }
+
+ private class SharedPreferenceChangedListener implements
+ SharedPreferences.OnSharedPreferenceChangeListener {
+ @Override
+ public void onSharedPreferenceChanged(
+ SharedPreferences sharedPreferences, String key) {
+ if (TextUtils.equals(key, OPERATOR_BRAND_OVERRIDE_PREFIX
+ + mPhone.getIccSerialNumber())) {
+ // SPN override from carrier privileged apps
+ logd("[onSharedPreferenceChanged]: " + key);
+ sendEmptyMessage(SPN_OVERRIDE_EVENT);
+ }
+ }
+ }
+
+ public CarrierIdentifier(Phone phone) {
+ logd("Creating CarrierIdentifier[" + phone.getPhoneId() + "]");
+ mContext = phone.getContext();
+ mPhone = phone;
+ mTelephonyMgr = TelephonyManager.from(mContext);
+
+ // register events
+ mContext.getContentResolver().registerContentObserver(CONTENT_URL_PREFER_APN, false,
+ mContentObserver);
+ mContext.getContentResolver().registerContentObserver(
+ Telephony.CarrierIdentification.CONTENT_URI, false, mContentObserver);
+ SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
+ mOnSubscriptionsChangedListener);
+ PreferenceManager.getDefaultSharedPreferences(mContext)
+ .registerOnSharedPreferenceChangeListener(mSharedPrefListener);
+ UiccController.getInstance().registerForIccChanged(this, ICC_CHANGED_EVENT, null);
+ }
+
+ /**
+ * Entry point for the carrier identification.
+ *
+ * 1. SIM_LOAD_EVENT
+ * This indicates that all SIM records has been loaded and its first entry point for the
+ * carrier identification. Note, there are other attributes could be changed on the fly
+ * like APN and SPN. We cached all carrier matching rules based on MCCMNC to speed
+ * up carrier resolution on following trigger events.
+ *
+ * 2. PREFER_APN_UPDATE_EVENT
+ * This indicates prefer apn has been changed. It could be triggered when user modified
+ * APN settings or when default data connection first establishes on the current carrier.
+ * We follow up on this by querying prefer apn sqlite and re-issue carrier identification
+ * with the updated prefer apn name.
+ *
+ * 3. SPN_OVERRIDE_EVENT
+ * This indicates that SPN value as been changed. It could be triggered from EF_SPN
+ * record loading, carrier config override
+ * {@link android.telephony.CarrierConfigManager#KEY_CARRIER_NAME_STRING}
+ * or carrier app override {@link TelephonyManager#setOperatorBrandOverride(String)}.
+ * we follow up this by checking the cached mSPN against the latest value and issue
+ * carrier identification only if spn changes.
+ *
+ * 4. CARRIER_ID_DB_UPDATE_EVENT
+ * This indicates that carrierIdentification database which stores all matching rules
+ * has been updated. It could be triggered from OTA or assets update.
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ if (VDBG) logd("handleMessage: " + msg.what);
+ switch (msg.what) {
+ case SIM_LOAD_EVENT:
+ case CARRIER_ID_DB_UPDATE_EVENT:
+ mSpn = mTelephonyMgr.getSimOperatorNameForPhone(mPhone.getPhoneId());
+ mPreferApn = getPreferApn();
+ loadCarrierMatchingRulesOnMccMnc();
+ break;
+ case SIM_ABSENT_EVENT:
+ mCarrierMatchingRulesOnMccMnc.clear();
+ mSpn = null;
+ mPreferApn = null;
+ updateCarrierIdAndName(INVALID_CARRIER_ID, null);
+ break;
+ case PREFER_APN_UPDATE_EVENT:
+ String preferApn = getPreferApn();
+ if (!equals(mPreferApn, preferApn, true)) {
+ logd("[updatePreferApn] from:" + mPreferApn + " to:" + preferApn);
+ mPreferApn = preferApn;
+ matchCarrier();
+ }
+ break;
+ case SPN_OVERRIDE_EVENT:
+ String spn = mTelephonyMgr.getSimOperatorNameForPhone(mPhone.getPhoneId());
+ if (!equals(mSpn, spn, true)) {
+ logd("[updateSpn] from:" + mSpn + " to:" + spn);
+ mSpn = spn;
+ matchCarrier();
+ }
+ break;
+ case ICC_CHANGED_EVENT:
+ IccRecords newIccRecords = UiccController.getInstance().getIccRecords(
+ mPhone.getPhoneId(), UiccController.APP_FAM_3GPP);
+ if (mIccRecords != newIccRecords) {
+ if (mIccRecords != null) {
+ logd("Removing stale icc objects.");
+ mIccRecords.unregisterForSpnUpdate(this);
+ mIccRecords = null;
+ }
+ if (newIccRecords != null) {
+ logd("new Icc object");
+ newIccRecords.registerForSpnUpdate(this, SPN_OVERRIDE_EVENT, null);
+ mIccRecords = newIccRecords;
+ }
+ }
+ break;
+ default:
+ loge("invalid msg: " + msg.what);
+ break;
+ }
+ }
+
+ private void loadCarrierMatchingRulesOnMccMnc() {
+ try {
+ String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId());
+ Cursor cursor = mContext.getContentResolver().query(CarrierIdentification.CONTENT_URI,
+ /* projection */ null,
+ /* selection */ CarrierIdentification.MCCMNC + "=?",
+ /* selectionArgs */ new String[]{mccmnc}, null);
+ try {
+ if (cursor != null) {
+ if (VDBG) {
+ logd("[loadCarrierMatchingRules]- " + cursor.getCount()
+ + " Records(s) in DB" + " mccmnc: " + mccmnc);
+ }
+ mCarrierMatchingRulesOnMccMnc.clear();
+ while (cursor.moveToNext()) {
+ mCarrierMatchingRulesOnMccMnc.add(makeCarrierMatchingRule(cursor));
+ }
+ matchCarrier();
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ } catch (Exception ex) {
+ loge("[loadCarrierMatchingRules]- ex: " + ex);
+ }
+ }
+
+ private String getPreferApn() {
+ Cursor cursor = mContext.getContentResolver().query(
+ Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "preferapn/subId/"
+ + mPhone.getSubId()), /* projection */ new String[]{Telephony.Carriers.APN},
+ /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null);
+ try {
+ if (cursor != null) {
+ if (VDBG) {
+ logd("[getPreferApn]- " + cursor.getCount() + " Records(s) in DB");
+ }
+ while (cursor.moveToNext()) {
+ String apn = cursor.getString(cursor.getColumnIndexOrThrow(
+ Telephony.Carriers.APN));
+ logd("[getPreferApn]- " + apn);
+ return apn;
+ }
+ }
+ } catch (Exception ex) {
+ loge("[getPreferApn]- exception: " + ex);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
+ private void updateCarrierIdAndName(int cid, String name) {
+ boolean update = false;
+ if (!equals(name, mCarrierName, true)) {
+ logd("[updateCarrierName] from:" + mCarrierName + " to:" + name);
+ mCarrierName = name;
+ update = true;
+ }
+ if (cid != mCarrierId) {
+ logd("[updateCarrierId] from:" + mCarrierId + " to:" + cid);
+ mCarrierId = cid;
+ update = true;
+ }
+ if (update) {
+ // TODO new public intent CARRIER_ID_CHANGED
+ mCarrierIdLocalLog.log("[updateCarrierIdAndName] cid:" + mCarrierId + " name:"
+ + mCarrierName);
+ }
+ }
+
+ private CarrierMatchingRule makeCarrierMatchingRule(Cursor cursor) {
+ return new CarrierMatchingRule(
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.MCCMNC)),
+ cursor.getString(cursor.getColumnIndexOrThrow(
+ CarrierIdentification.IMSI_PREFIX_XPATTERN)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.GID1)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.GID2)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.PLMN)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.SPN)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.APN)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(CarrierIdentification.CID)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.NAME)));
+ }
+
+ /**
+ * carrier matching attributes with corresponding cid
+ */
+ private static class CarrierMatchingRule {
+ /**
+ * These scores provide the hierarchical relationship between the attributes, intended to
+ * resolve conflicts in a deterministic way. The scores are constructed such that a match
+ * from a higher tier will beat any subsequent match which does not match at that tier,
+ * so MCCMNC beats everything else. This avoids problems when two (or more) carriers rule
+ * matches as the score helps to find the best match uniquely. e.g.,
+ * rule 1 {mccmnc, imsi} rule 2 {mccmnc, imsi, gid1} and rule 3 {mccmnc, imsi, gid2} all
+ * matches with subscription data. rule 2 wins with the highest matching score.
+ */
+ private static final int SCORE_MCCMNC = 1 << 6;
+ private static final int SCORE_IMSI_PREFIX = 1 << 5;
+ private static final int SCORE_GID1 = 1 << 4;
+ private static final int SCORE_GID2 = 1 << 3;
+ private static final int SCORE_PLMN = 1 << 2;
+ private static final int SCORE_SPN = 1 << 1;
+ private static final int SCORE_APN = 1 << 0;
+
+ private static final int SCORE_INVALID = -1;
+
+ // carrier matching attributes
+ private String mMccMnc;
+ private String mImsiPrefixPattern;
+ private String mGid1;
+ private String mGid2;
+ private String mPlmn;
+ private String mSpn;
+ private String mApn;
+
+ // user-facing carrier name
+ private String mName;
+ // unique carrier id
+ private int mCid;
+
+ private int mScore = 0;
+
+ CarrierMatchingRule(String mccmnc, String imsiPrefixPattern, String gid1, String gid2,
+ String plmn, String spn, String apn, int cid, String name) {
+ mMccMnc = mccmnc;
+ mImsiPrefixPattern = imsiPrefixPattern;
+ mGid1 = gid1;
+ mGid2 = gid2;
+ mPlmn = plmn;
+ mSpn = spn;
+ mApn = apn;
+ mCid = cid;
+ mName = name;
+ }
+
+ // Calculate matching score. Values which aren't set in the rule are considered "wild".
+ // All values in the rule must match in order for the subscription to be considered part of
+ // the carrier. otherwise, a invalid score -1 will be assigned. A match from a higher tier
+ // will beat any subsequent match which does not match at that tier. When there are multiple
+ // matches at the same tier, the longest, best match will be used.
+ public void match(CarrierMatchingRule subscriptionRule) {
+ mScore = 0;
+ if (mMccMnc != null) {
+ if (!CarrierIdentifier.equals(subscriptionRule.mMccMnc, mMccMnc, false)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_MCCMNC;
+ }
+ if (mImsiPrefixPattern != null) {
+ if (!imsiPrefixMatch(subscriptionRule.mImsiPrefixPattern, mImsiPrefixPattern)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_IMSI_PREFIX;
+ }
+ if (mGid1 != null) {
+ // full string match. carrier matching should cover the corner case that gid1
+ // with garbage tail due to SIM manufacture issues.
+ if (!CarrierIdentifier.equals(subscriptionRule.mGid1, mGid1, true)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_GID1;
+ }
+ if (mGid2 != null) {
+ // full string match. carrier matching should cover the corner case that gid2
+ // with garbage tail due to SIM manufacture issues.
+ if (!CarrierIdentifier.equals(subscriptionRule.mGid2, mGid2, true)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_GID2;
+ }
+ if (mPlmn != null) {
+ if (!CarrierIdentifier.equals(subscriptionRule.mPlmn, mPlmn, true)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_PLMN;
+ }
+ if (mSpn != null) {
+ if (!CarrierIdentifier.equals(subscriptionRule.mSpn, mSpn, true)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_SPN;
+ }
+ if (mApn != null) {
+ if (!CarrierIdentifier.equals(subscriptionRule.mApn, mApn, true)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_APN;
+ }
+ }
+
+ private boolean imsiPrefixMatch(String imsi, String prefixXPattern) {
+ if (TextUtils.isEmpty(prefixXPattern)) return true;
+ if (TextUtils.isEmpty(imsi)) return false;
+ if (imsi.length() < prefixXPattern.length()) {
+ return false;
+ }
+ for (int i = 0; i < prefixXPattern.length(); i++) {
+ if ((prefixXPattern.charAt(i) != 'x') && (prefixXPattern.charAt(i) != 'X')
+ && (prefixXPattern.charAt(i) != imsi.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public String toString() {
+ return "[CarrierMatchingRule] -"
+ + " mccmnc: " + mMccMnc
+ + " gid1: " + mGid1
+ + " gid2: " + mGid2
+ + " plmn: " + mPlmn
+ + " imsi_prefix: " + mImsiPrefixPattern
+ + " spn: " + mSpn
+ + " apn: " + mApn
+ + " name: " + mName
+ + " cid: " + mCid
+ + " score: " + mScore;
+ }
+ }
+
+ /**
+ * find the best matching carrier from candidates with matched MCCMNC and notify
+ * all interested parties on carrier id change.
+ */
+ private void matchCarrier() {
+ if (!SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) {
+ logd("[matchCarrier]" + "skip before sim records loaded");
+ return;
+ }
+ final String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId());
+ final String gid1 = mPhone.getGroupIdLevel1();
+ final String gid2 = mPhone.getGroupIdLevel2();
+ final String imsi = mPhone.getSubscriberId();
+ final String plmn = mPhone.getPlmn();
+ final String spn = mSpn;
+ final String apn = mPreferApn;
+
+ if (VDBG) {
+ logd("[matchCarrier]"
+ + " gid1: " + gid1
+ + " gid2: " + gid2
+ + " imsi: " + Rlog.pii(LOG_TAG, imsi)
+ + " plmn: " + plmn
+ + " spn: " + spn
+ + " apn: " + apn);
+ }
+
+ CarrierMatchingRule subscriptionRule = new CarrierMatchingRule(
+ mccmnc, imsi, gid1, gid2, plmn, spn, apn, INVALID_CARRIER_ID, null);
+
+ int maxScore = CarrierMatchingRule.SCORE_INVALID;
+ CarrierMatchingRule maxRule = null;
+
+ for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) {
+ rule.match(subscriptionRule);
+ if (rule.mScore > maxScore) {
+ maxScore = rule.mScore;
+ maxRule = rule;
+ }
+ }
+ if (maxScore == CarrierMatchingRule.SCORE_INVALID) {
+ logd("[matchCarrier - no match] cid: " + INVALID_CARRIER_ID + " name: " + null);
+ updateCarrierIdAndName(INVALID_CARRIER_ID, null);
+ } else {
+ logd("[matchCarrier] cid: " + maxRule.mCid + " name: " + maxRule.mName);
+ updateCarrierIdAndName(maxRule.mCid, maxRule.mName);
+ }
+ }
+
+ public int getCarrierId() {
+ return mCarrierId;
+ }
+
+ public String getCarrierName() {
+ return mCarrierName;
+ }
+
+ private static boolean equals(String a, String b, boolean ignoreCase) {
+ if (a == null && b == null) return true;
+ if (a != null && b != null) {
+ return (ignoreCase) ? a.equalsIgnoreCase(b) : a.equals(b);
+ }
+ return false;
+ }
+
+ private static void logd(String str) {
+ Rlog.d(LOG_TAG, str);
+ }
+ private static void loge(String str) {
+ Rlog.e(LOG_TAG, str);
+ }
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("mCarrierIdLocalLogs:");
+ ipw.increaseIndent();
+ mCarrierIdLocalLog.dump(fd, pw, args);
+ ipw.decreaseIndent();
+
+ ipw.println("mCarrierId: " + mCarrierId);
+ ipw.println("mCarrierName: " + mCarrierName);
+
+ ipw.println("mCarrierMatchingRules on mccmnc: "
+ + mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId()));
+ ipw.increaseIndent();
+ for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) {
+ ipw.println(rule.toString());
+ }
+ ipw.decreaseIndent();
+
+ ipw.println("mSpn: " + mSpn);
+ ipw.println("mPreferApn: " + mPreferApn);
+ ipw.flush();
+ }
+}
diff --git a/com/android/internal/telephony/CarrierInfoManager.java b/com/android/internal/telephony/CarrierInfoManager.java
index ebf04e89..d224c7df 100644
--- a/com/android/internal/telephony/CarrierInfoManager.java
+++ b/com/android/internal/telephony/CarrierInfoManager.java
@@ -38,30 +38,30 @@ public class CarrierInfoManager {
/**
* Returns Carrier specific information that will be used to encrypt the IMSI and IMPI.
* @param keyType whether the key is being used for WLAN or ePDG.
- * @param mContext
+ * @param context
* @return ImsiEncryptionInfo which contains the information, including the public key, to be
* used for encryption.
*/
public static ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType,
- Context mContext) {
+ Context context) {
String mcc = "";
String mnc = "";
final TelephonyManager telephonyManager =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- String networkOperator = telephonyManager.getNetworkOperator();
- if (!TextUtils.isEmpty(networkOperator)) {
- mcc = networkOperator.substring(0, 3);
- mnc = networkOperator.substring(3);
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ String simOperator = telephonyManager.getSimOperator();
+ if (!TextUtils.isEmpty(simOperator)) {
+ mcc = simOperator.substring(0, 3);
+ mnc = simOperator.substring(3);
Log.i(LOG_TAG, "using values for mnc, mcc: " + mnc + "," + mcc);
} else {
- Log.e(LOG_TAG, "Invalid networkOperator: " + networkOperator);
+ Log.e(LOG_TAG, "Invalid networkOperator: " + simOperator);
return null;
}
Cursor findCursor = null;
try {
// In the current design, MVNOs are not supported. If we decide to support them,
// we'll need to add to this CL.
- ContentResolver mContentResolver = mContext.getContentResolver();
+ ContentResolver mContentResolver = context.getContentResolver();
String[] columns = {Telephony.CarrierColumns.PUBLIC_KEY,
Telephony.CarrierColumns.EXPIRATION_TIME,
Telephony.CarrierColumns.KEY_IDENTIFIER};
@@ -95,12 +95,12 @@ public class CarrierInfoManager {
/**
* Inserts or update the Carrier Key in the database
* @param imsiEncryptionInfo ImsiEncryptionInfo object.
- * @param mContext Context.
+ * @param context Context.
*/
public static void updateOrInsertCarrierKey(ImsiEncryptionInfo imsiEncryptionInfo,
- Context mContext) {
+ Context context) {
byte[] keyBytes = imsiEncryptionInfo.getPublicKey().getEncoded();
- ContentResolver mContentResolver = mContext.getContentResolver();
+ ContentResolver mContentResolver = context.getContentResolver();
// In the current design, MVNOs are not supported. If we decide to support them,
// we'll need to add to this CL.
ContentValues contentValues = new ContentValues();
@@ -150,12 +150,54 @@ public class CarrierInfoManager {
* {@link java.security.PublicKey} and the Key Identifier.
* The keyIdentifier Attribute value pair that helps a server locate
* the private key to decrypt the permanent identity.
- * @param mContext Context.
+ * @param context Context.
*/
public static void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
- Context mContext) {
+ Context context) {
Log.i(LOG_TAG, "inserting carrier key: " + imsiEncryptionInfo);
- updateOrInsertCarrierKey(imsiEncryptionInfo, mContext);
+ updateOrInsertCarrierKey(imsiEncryptionInfo, context);
//todo send key to modem. Will be done in a subsequent CL.
}
+
+ /**
+ * Deletes all the keys for a given Carrier from the device keystore.
+ * @param context Context
+ */
+ public static void deleteCarrierInfoForImsiEncryption(Context context) {
+ Log.i(LOG_TAG, "deleting carrier key from db");
+ String mcc = "";
+ String mnc = "";
+ final TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ String simOperator = telephonyManager.getSimOperator();
+ if (!TextUtils.isEmpty(simOperator)) {
+ mcc = simOperator.substring(0, 3);
+ mnc = simOperator.substring(3);
+ } else {
+ Log.e(LOG_TAG, "Invalid networkOperator: " + simOperator);
+ return;
+ }
+ ContentResolver mContentResolver = context.getContentResolver();
+ try {
+ String whereClause = "mcc=? and mnc=?";
+ String[] whereArgs = new String[] { mcc, mnc };
+ mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, whereClause, whereArgs);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Delete failed" + e);
+ }
+ }
+
+ /**
+ * Deletes all the keys from the device keystore.
+ * @param context Context
+ */
+ public static void deleteAllCarrierKeysForImsiEncryption(Context context) {
+ Log.i(LOG_TAG, "deleting ALL carrier keys from db");
+ ContentResolver mContentResolver = context.getContentResolver();
+ try {
+ mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, null, null);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Delete failed" + e);
+ }
+ }
} \ No newline at end of file
diff --git a/com/android/internal/telephony/CommandsInterface.java b/com/android/internal/telephony/CommandsInterface.java
index 7026ff22..d5eb5463 100644
--- a/com/android/internal/telephony/CommandsInterface.java
+++ b/com/android/internal/telephony/CommandsInterface.java
@@ -23,9 +23,9 @@ import android.service.carrier.CarrierIdentifier;
import android.telephony.ClientRequestStats;
import android.telephony.ImsiEncryptionInfo;
import android.telephony.NetworkScanRequest;
+import android.telephony.data.DataProfile;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
-import com.android.internal.telephony.dataconnection.DataProfile;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import com.android.internal.telephony.uicc.IccCardStatus;
diff --git a/com/android/internal/telephony/GsmCdmaPhone.java b/com/android/internal/telephony/GsmCdmaPhone.java
index 47289e57..64804177 100644
--- a/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/com/android/internal/telephony/GsmCdmaPhone.java
@@ -157,6 +157,8 @@ public class GsmCdmaPhone extends Phone {
private ArrayList <MmiCode> mPendingMMIs = new ArrayList<MmiCode>();
private IccPhoneBookInterfaceManager mIccPhoneBookIntManager;
private DeviceStateMonitor mDeviceStateMonitor;
+ // Used for identify the carrier of current subscription
+ private CarrierIdentifier mCarrerIdentifier;
private int mPrecisePhoneType;
@@ -212,6 +214,8 @@ public class GsmCdmaPhone extends Phone {
mSST = mTelephonyComponentFactory.makeServiceStateTracker(this, this.mCi);
// DcTracker uses SST so needs to be created after it is instantiated
mDcTracker = mTelephonyComponentFactory.makeDcTracker(this);
+ mCarrerIdentifier = mTelephonyComponentFactory.makeCarrierIdentifier(this);
+
mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
mDeviceStateMonitor = mTelephonyComponentFactory.makeDeviceStateMonitor(this);
logd("GsmCdmaPhone: constructor: sub = " + mPhoneId);
@@ -1537,6 +1541,16 @@ public class GsmCdmaPhone extends Phone {
}
@Override
+ public int getCarrierId() {
+ return mCarrerIdentifier.getCarrierId();
+ }
+
+ @Override
+ public String getCarrierName() {
+ return mCarrerIdentifier.getCarrierName();
+ }
+
+ @Override
public String getGroupIdLevel1() {
if (isPhoneTypeGsm()) {
IccRecords r = mIccRecords.get();
@@ -1573,6 +1587,19 @@ public class GsmCdmaPhone extends Phone {
}
@Override
+ public String getPlmn() {
+ if (isPhoneTypeGsm()) {
+ IccRecords r = mIccRecords.get();
+ return (r != null) ? r.getPnnHomeName() : null;
+ } else if (isPhoneTypeCdma()) {
+ loge("Plmn is not available in CDMA");
+ return null;
+ } else { //isPhoneTypeCdmaLte()
+ return (mSimRecords != null) ? mSimRecords.getPnnHomeName() : null;
+ }
+ }
+
+ @Override
public String getCdmaPrlVersion() {
return mSST.getPrlVersion();
}
diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java
index c51a5fe4..d702c09b 100644
--- a/com/android/internal/telephony/Phone.java
+++ b/com/android/internal/telephony/Phone.java
@@ -2881,6 +2881,13 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
}
/**
+ * Retrieves the EF_PNN from the UICC For GSM/UMTS phones.
+ */
+ public String getPlmn() {
+ return null;
+ }
+
+ /**
* Get the current for the default apn DataState. No change notification
* exists at this interface -- use
* {@link android.telephony.PhoneStateListener} instead.
@@ -2985,6 +2992,15 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
return;
}
+ public int getCarrierId() {
+ // TODO remove hardcoding and expose a public API for INVALID CARRIER ID
+ return -1;
+ }
+
+ public String getCarrierName() {
+ return null;
+ }
+
/**
* Return if UT capability of ImsPhone is enabled or not
*/
diff --git a/com/android/internal/telephony/RIL.java b/com/android/internal/telephony/RIL.java
index 3ddbf121..2d334982 100644
--- a/com/android/internal/telephony/RIL.java
+++ b/com/android/internal/telephony/RIL.java
@@ -54,6 +54,7 @@ import android.hardware.radio.V1_0.SmsWriteArgs;
import android.hardware.radio.V1_0.UusInfo;
import android.net.ConnectivityManager;
import android.os.AsyncResult;
+import android.os.Build;
import android.os.Handler;
import android.os.HwBinder;
import android.os.Message;
@@ -80,15 +81,17 @@ import android.telephony.SignalStrength;
import android.telephony.SmsManager;
import android.telephony.TelephonyHistogram;
import android.telephony.TelephonyManager;
+import android.telephony.data.DataProfile;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.cat.ComprehensionTlv;
+import com.android.internal.telephony.cat.ComprehensionTlvTag;
import com.android.internal.telephony.cdma.CdmaInformationRecords;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
import com.android.internal.telephony.dataconnection.DataCallResponse;
-import com.android.internal.telephony.dataconnection.DataProfile;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.nano.TelephonyProto.SmsSession;
@@ -189,6 +192,9 @@ public class RIL extends BaseCommands implements CommandsInterface {
static final int IRADIO_GET_SERVICE_DELAY_MILLIS = 4 * 1000;
+ static final String EMPTY_ALPHA_LONG = "";
+ static final String EMPTY_ALPHA_SHORT = "";
+
public static List<TelephonyHistogram> getTelephonyRILTimingHistograms() {
List<TelephonyHistogram> list;
synchronized (mRilTimeHistograms) {
@@ -1061,23 +1067,23 @@ public class RIL extends BaseCommands implements CommandsInterface {
private static DataProfileInfo convertToHalDataProfile(DataProfile dp) {
DataProfileInfo dpi = new DataProfileInfo();
- dpi.profileId = dp.profileId;
- dpi.apn = dp.apn;
- dpi.protocol = dp.protocol;
- dpi.roamingProtocol = dp.roamingProtocol;
- dpi.authType = dp.authType;
- dpi.user = dp.user;
- dpi.password = dp.password;
- dpi.type = dp.type;
- dpi.maxConnsTime = dp.maxConnsTime;
- dpi.maxConns = dp.maxConns;
- dpi.waitTime = dp.waitTime;
- dpi.enabled = dp.enabled;
- dpi.supportedApnTypesBitmap = dp.supportedApnTypesBitmap;
- dpi.bearerBitmap = dp.bearerBitmap;
- dpi.mtu = dp.mtu;
- dpi.mvnoType = convertToHalMvnoType(dp.mvnoType);
- dpi.mvnoMatchData = dp.mvnoMatchData;
+ dpi.profileId = dp.getProfileId();
+ dpi.apn = dp.getApn();
+ dpi.protocol = dp.getProtocol();
+ dpi.roamingProtocol = dp.getRoamingProtocol();
+ dpi.authType = dp.getAuthType();
+ dpi.user = dp.getUserName();
+ dpi.password = dp.getPassword();
+ dpi.type = dp.getType();
+ dpi.maxConnsTime = dp.getMaxConnsTime();
+ dpi.maxConns = dp.getMaxConns();
+ dpi.waitTime = dp.getWaitTime();
+ dpi.enabled = dp.isEnabled();
+ dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmap();
+ dpi.bearerBitmap = dp.getBearerBitmap();
+ dpi.mtu = dp.getMtu();
+ dpi.mvnoType = convertToHalMvnoType(dp.getMvnoType());
+ dpi.mvnoMatchData = dp.getMvnoMatchData();
return dpi;
}
@@ -1143,7 +1149,7 @@ public class RIL extends BaseCommands implements CommandsInterface {
try {
radioProxy.setupDataCall(rr.mSerial, radioTechnology, dpi,
- dataProfile.modemCognitive, allowRoaming, isRoaming);
+ dataProfile.isModemCognitive(), allowRoaming, isRoaming);
mMetrics.writeRilSetupDataCall(mPhoneId, rr.mSerial, radioTechnology, dpi.profileId,
dpi.apn, dpi.authType, dpi.protocol);
} catch (RemoteException | RuntimeException e) {
@@ -1167,12 +1173,16 @@ public class RIL extends BaseCommands implements CommandsInterface {
mRILDefaultWorkSource);
if (RILJ_LOGD) {
- riljLog(rr.serialString() + "> iccIO: "
- + requestToString(rr.mRequest) + " command = 0x"
- + Integer.toHexString(command) + " fileId = 0x"
- + Integer.toHexString(fileId) + " path = " + path + " p1 = "
- + p1 + " p2 = " + p2 + " p3 = " + " data = " + data
- + " aid = " + aid);
+ if (Build.IS_DEBUGGABLE) {
+ riljLog(rr.serialString() + "> iccIO: "
+ + requestToString(rr.mRequest) + " command = 0x"
+ + Integer.toHexString(command) + " fileId = 0x"
+ + Integer.toHexString(fileId) + " path = " + path + " p1 = "
+ + p1 + " p2 = " + p2 + " p3 = " + " data = " + data
+ + " aid = " + aid);
+ } else {
+ riljLog(rr.serialString() + "> iccIO: " + requestToString(rr.mRequest));
+ }
}
IccIo iccIo = new IccIo();
@@ -2027,7 +2037,7 @@ public class RIL extends BaseCommands implements CommandsInterface {
if (RILJ_LOGD) {
riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " contents = "
- + contents);
+ + (Build.IS_DEBUGGABLE ? contents : censoredTerminalResponse(contents)));
}
try {
@@ -2039,6 +2049,33 @@ public class RIL extends BaseCommands implements CommandsInterface {
}
}
+ private String censoredTerminalResponse(String terminalResponse) {
+ try {
+ byte[] bytes = IccUtils.hexStringToBytes(terminalResponse);
+ if (bytes != null) {
+ List<ComprehensionTlv> ctlvs = ComprehensionTlv.decodeMany(bytes, 0);
+ int from = 0;
+ for (ComprehensionTlv ctlv : ctlvs) {
+ // Find text strings which might be personal information input by user,
+ // then replace it with "********".
+ if (ComprehensionTlvTag.TEXT_STRING.value() == ctlv.getTag()) {
+ byte[] target = Arrays.copyOfRange(ctlv.getRawValue(), from,
+ ctlv.getValueIndex() + ctlv.getLength());
+ terminalResponse = terminalResponse.toLowerCase().replace(
+ IccUtils.bytesToHexString(target), "********");
+ }
+ // The text string tag and the length field should also be hidden.
+ from = ctlv.getValueIndex() + ctlv.getLength();
+ }
+ }
+ } catch (Exception e) {
+ Rlog.e(RILJ_LOG_TAG, "Could not censor the terminal response: " + e);
+ terminalResponse = null;
+ }
+
+ return terminalResponse;
+ }
+
@Override
public void sendEnvelopeWithStatus(String contents, Message result) {
IRadio radioProxy = getRadioProxy(result);
@@ -2864,7 +2901,7 @@ public class RIL extends BaseCommands implements CommandsInterface {
try {
radioProxy.setInitialAttachApn(rr.mSerial, convertToHalDataProfile(dataProfile),
- dataProfile.modemCognitive, isRoaming);
+ dataProfile.isModemCognitive(), isRoaming);
} catch (RemoteException | RuntimeException e) {
handleRadioProxyExceptionForRR(rr, "setInitialAttachApn", e);
}
@@ -2969,9 +3006,13 @@ public class RIL extends BaseCommands implements CommandsInterface {
mRILDefaultWorkSource);
if (RILJ_LOGD) {
- riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
- + " cla = " + cla + " instruction = " + instruction
- + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data);
+ if (Build.IS_DEBUGGABLE) {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " cla = " + cla + " instruction = " + instruction
+ + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data);
+ } else {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+ }
}
SimApdu msg = createSimApdu(0, cla, instruction, p1, p2, p3, data);
@@ -2991,8 +3032,12 @@ public class RIL extends BaseCommands implements CommandsInterface {
mRILDefaultWorkSource);
if (RILJ_LOGD) {
- riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " aid = " + aid
- + " p2 = " + p2);
+ if (Build.IS_DEBUGGABLE) {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " aid = " + aid
+ + " p2 = " + p2);
+ } else {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+ }
}
try {
@@ -3038,9 +3083,13 @@ public class RIL extends BaseCommands implements CommandsInterface {
mRILDefaultWorkSource);
if (RILJ_LOGD) {
- riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " channel = "
- + channel + " cla = " + cla + " instruction = " + instruction
- + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data);
+ if (Build.IS_DEBUGGABLE) {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " channel = "
+ + channel + " cla = " + cla + " instruction = " + instruction
+ + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data);
+ } else {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+ }
}
SimApdu msg = createSimApdu(channel, cla, instruction, p1, p2, p3, data);
@@ -4813,7 +4862,80 @@ public class RIL extends BaseCommands implements CommandsInterface {
return capacityResponse;
}
- static ArrayList<CellInfo> convertHalCellInfoList(
+ private static void writeToParcelForGsm(
+ Parcel p, int lac, int cid, int arfcn, int bsic, String mcc, String mnc,
+ String al, String as, int ss, int ber, int ta) {
+ p.writeInt(lac);
+ p.writeInt(cid);
+ p.writeInt(arfcn);
+ p.writeInt(bsic);
+ p.writeString(mcc);
+ p.writeString(mnc);
+ p.writeString(al);
+ p.writeString(as);
+ p.writeInt(ss);
+ p.writeInt(ber);
+ p.writeInt(ta);
+ }
+
+ private static void writeToParcelForCdma(
+ Parcel p, int ni, int si, int bsi, int lon, int lat, String al, String as,
+ int dbm, int ecio, int eDbm, int eEcio, int eSnr) {
+ p.writeInt(ni);
+ p.writeInt(si);
+ p.writeInt(bsi);
+ p.writeInt(lon);
+ p.writeInt(lat);
+ p.writeString(al);
+ p.writeString(as);
+ p.writeInt(dbm);
+ p.writeInt(ecio);
+ p.writeInt(eDbm);
+ p.writeInt(eEcio);
+ p.writeInt(eSnr);
+ }
+
+ private static void writeToParcelForLte(
+ Parcel p, int ci, int pci, int tac, int earfcn, String mcc, String mnc, String al,
+ String as, int ss, int rsrp, int rsrq, int rssnr, int cqi, int ta) {
+ p.writeInt(ci);
+ p.writeInt(pci);
+ p.writeInt(tac);
+ p.writeInt(earfcn);
+ p.writeString(mcc);
+ p.writeString(mnc);
+ p.writeString(al);
+ p.writeString(as);
+ p.writeInt(ss);
+ p.writeInt(rsrp);
+ p.writeInt(rsrq);
+ p.writeInt(rssnr);
+ p.writeInt(cqi);
+ p.writeInt(ta);
+ }
+
+ private static void writeToParcelForWcdma(
+ Parcel p, int lac, int cid, int psc, int uarfcn, String mcc, String mnc,
+ String al, String as, int ss, int ber) {
+ p.writeInt(lac);
+ p.writeInt(cid);
+ p.writeInt(psc);
+ p.writeInt(uarfcn);
+ p.writeString(mcc);
+ p.writeString(mnc);
+ p.writeString(al);
+ p.writeString(as);
+ p.writeInt(ss);
+ p.writeInt(ber);
+ }
+
+ /**
+ * Convert CellInfo defined in 1.0/types.hal to CellInfo type.
+ * @param records List of CellInfo defined in 1.0/types.hal
+ * @return List of converted CellInfo object
+ */
+ @VisibleForTesting
+ public static ArrayList<CellInfo> convertHalCellInfoList(
ArrayList<android.hardware.radio.V1_0.CellInfo> records) {
ArrayList<CellInfo> response = new ArrayList<CellInfo>(records.size());
@@ -4827,60 +4949,182 @@ public class RIL extends BaseCommands implements CommandsInterface {
switch (record.cellInfoType) {
case CellInfoType.GSM: {
CellInfoGsm cellInfoGsm = record.gsm.get(0);
- p.writeInt(Integer.parseInt(cellInfoGsm.cellIdentityGsm.mcc));
- p.writeInt(Integer.parseInt(cellInfoGsm.cellIdentityGsm.mnc));
- p.writeInt(cellInfoGsm.cellIdentityGsm.lac);
- p.writeInt(cellInfoGsm.cellIdentityGsm.cid);
- p.writeInt(cellInfoGsm.cellIdentityGsm.arfcn);
- p.writeInt(Byte.toUnsignedInt(cellInfoGsm.cellIdentityGsm.bsic));
- p.writeInt(cellInfoGsm.signalStrengthGsm.signalStrength);
- p.writeInt(cellInfoGsm.signalStrengthGsm.bitErrorRate);
- p.writeInt(cellInfoGsm.signalStrengthGsm.timingAdvance);
+ writeToParcelForGsm(
+ p,
+ cellInfoGsm.cellIdentityGsm.lac,
+ cellInfoGsm.cellIdentityGsm.cid,
+ cellInfoGsm.cellIdentityGsm.arfcn,
+ Byte.toUnsignedInt(cellInfoGsm.cellIdentityGsm.bsic),
+ cellInfoGsm.cellIdentityGsm.mcc,
+ cellInfoGsm.cellIdentityGsm.mnc,
+ EMPTY_ALPHA_LONG,
+ EMPTY_ALPHA_SHORT,
+ cellInfoGsm.signalStrengthGsm.signalStrength,
+ cellInfoGsm.signalStrengthGsm.bitErrorRate,
+ cellInfoGsm.signalStrengthGsm.timingAdvance);
break;
}
case CellInfoType.CDMA: {
CellInfoCdma cellInfoCdma = record.cdma.get(0);
- p.writeInt(cellInfoCdma.cellIdentityCdma.networkId);
- p.writeInt(cellInfoCdma.cellIdentityCdma.systemId);
- p.writeInt(cellInfoCdma.cellIdentityCdma.baseStationId);
- p.writeInt(cellInfoCdma.cellIdentityCdma.longitude);
- p.writeInt(cellInfoCdma.cellIdentityCdma.latitude);
- p.writeInt(cellInfoCdma.signalStrengthCdma.dbm);
- p.writeInt(cellInfoCdma.signalStrengthCdma.ecio);
- p.writeInt(cellInfoCdma.signalStrengthEvdo.dbm);
- p.writeInt(cellInfoCdma.signalStrengthEvdo.ecio);
- p.writeInt(cellInfoCdma.signalStrengthEvdo.signalNoiseRatio);
+ writeToParcelForCdma(
+ p,
+ cellInfoCdma.cellIdentityCdma.networkId,
+ cellInfoCdma.cellIdentityCdma.systemId,
+ cellInfoCdma.cellIdentityCdma.baseStationId,
+ cellInfoCdma.cellIdentityCdma.longitude,
+ cellInfoCdma.cellIdentityCdma.latitude,
+ EMPTY_ALPHA_LONG,
+ EMPTY_ALPHA_SHORT,
+ cellInfoCdma.signalStrengthCdma.dbm,
+ cellInfoCdma.signalStrengthCdma.ecio,
+ cellInfoCdma.signalStrengthEvdo.dbm,
+ cellInfoCdma.signalStrengthEvdo.ecio,
+ cellInfoCdma.signalStrengthEvdo.signalNoiseRatio);
break;
}
case CellInfoType.LTE: {
CellInfoLte cellInfoLte = record.lte.get(0);
- p.writeInt(Integer.parseInt(cellInfoLte.cellIdentityLte.mcc));
- p.writeInt(Integer.parseInt(cellInfoLte.cellIdentityLte.mnc));
- p.writeInt(cellInfoLte.cellIdentityLte.ci);
- p.writeInt(cellInfoLte.cellIdentityLte.pci);
- p.writeInt(cellInfoLte.cellIdentityLte.tac);
- p.writeInt(cellInfoLte.cellIdentityLte.earfcn);
- p.writeInt(cellInfoLte.signalStrengthLte.signalStrength);
- p.writeInt(cellInfoLte.signalStrengthLte.rsrp);
- p.writeInt(cellInfoLte.signalStrengthLte.rsrq);
- p.writeInt(cellInfoLte.signalStrengthLte.rssnr);
- p.writeInt(cellInfoLte.signalStrengthLte.cqi);
- p.writeInt(cellInfoLte.signalStrengthLte.timingAdvance);
+ writeToParcelForLte(
+ p,
+ cellInfoLte.cellIdentityLte.ci,
+ cellInfoLte.cellIdentityLte.pci,
+ cellInfoLte.cellIdentityLte.tac,
+ cellInfoLte.cellIdentityLte.earfcn,
+ cellInfoLte.cellIdentityLte.mcc,
+ cellInfoLte.cellIdentityLte.mnc,
+ EMPTY_ALPHA_LONG,
+ EMPTY_ALPHA_SHORT,
+ cellInfoLte.signalStrengthLte.signalStrength,
+ cellInfoLte.signalStrengthLte.rsrp,
+ cellInfoLte.signalStrengthLte.rsrq,
+ cellInfoLte.signalStrengthLte.rssnr,
+ cellInfoLte.signalStrengthLte.cqi,
+ cellInfoLte.signalStrengthLte.timingAdvance);
break;
}
case CellInfoType.WCDMA: {
CellInfoWcdma cellInfoWcdma = record.wcdma.get(0);
- p.writeInt(Integer.parseInt(cellInfoWcdma.cellIdentityWcdma.mcc));
- p.writeInt(Integer.parseInt(cellInfoWcdma.cellIdentityWcdma.mnc));
- p.writeInt(cellInfoWcdma.cellIdentityWcdma.lac);
- p.writeInt(cellInfoWcdma.cellIdentityWcdma.cid);
- p.writeInt(cellInfoWcdma.cellIdentityWcdma.psc);
- p.writeInt(cellInfoWcdma.cellIdentityWcdma.uarfcn);
- p.writeInt(cellInfoWcdma.signalStrengthWcdma.signalStrength);
- p.writeInt(cellInfoWcdma.signalStrengthWcdma.bitErrorRate);
+ writeToParcelForWcdma(
+ p,
+ cellInfoWcdma.cellIdentityWcdma.lac,
+ cellInfoWcdma.cellIdentityWcdma.cid,
+ cellInfoWcdma.cellIdentityWcdma.psc,
+ cellInfoWcdma.cellIdentityWcdma.uarfcn,
+ cellInfoWcdma.cellIdentityWcdma.mcc,
+ cellInfoWcdma.cellIdentityWcdma.mnc,
+ EMPTY_ALPHA_LONG,
+ EMPTY_ALPHA_SHORT,
+ cellInfoWcdma.signalStrengthWcdma.signalStrength,
+ cellInfoWcdma.signalStrengthWcdma.bitErrorRate);
+ break;
+ }
+
+ default:
+ throw new RuntimeException("unexpected cellinfotype: " + record.cellInfoType);
+ }
+
+ p.setDataPosition(0);
+ CellInfo InfoRec = CellInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+ response.add(InfoRec);
+ }
+
+ return response;
+ }
+
+ /**
+ * Convert CellInfo defined in 1.2/types.hal to CellInfo type.
+ * @param records List of CellInfo defined in 1.2/types.hal
+ * @return List of converted CellInfo object
+ */
+ @VisibleForTesting
+ public static ArrayList<CellInfo> convertHalCellInfoList_1_2(
+ ArrayList<android.hardware.radio.V1_2.CellInfo> records) {
+ ArrayList<CellInfo> response = new ArrayList<CellInfo>(records.size());
+
+ for (android.hardware.radio.V1_2.CellInfo record : records) {
+ // first convert RIL CellInfo to Parcel
+ Parcel p = Parcel.obtain();
+ p.writeInt(record.cellInfoType);
+ p.writeInt(record.registered ? 1 : 0);
+ p.writeInt(record.timeStampType);
+ p.writeLong(record.timeStamp);
+ switch (record.cellInfoType) {
+ case CellInfoType.GSM: {
+ android.hardware.radio.V1_2.CellInfoGsm cellInfoGsm = record.gsm.get(0);
+ writeToParcelForGsm(
+ p,
+ cellInfoGsm.cellIdentityGsm.base.lac,
+ cellInfoGsm.cellIdentityGsm.base.cid,
+ cellInfoGsm.cellIdentityGsm.base.arfcn,
+ Byte.toUnsignedInt(cellInfoGsm.cellIdentityGsm.base.bsic),
+ cellInfoGsm.cellIdentityGsm.base.mcc,
+ cellInfoGsm.cellIdentityGsm.base.mnc,
+ cellInfoGsm.cellIdentityGsm.operatorNames.alphaLong,
+ cellInfoGsm.cellIdentityGsm.operatorNames.alphaShort,
+ cellInfoGsm.signalStrengthGsm.signalStrength,
+ cellInfoGsm.signalStrengthGsm.bitErrorRate,
+ cellInfoGsm.signalStrengthGsm.timingAdvance);
+ break;
+ }
+
+ case CellInfoType.CDMA: {
+ android.hardware.radio.V1_2.CellInfoCdma cellInfoCdma = record.cdma.get(0);
+ writeToParcelForCdma(
+ p,
+ cellInfoCdma.cellIdentityCdma.base.networkId,
+ cellInfoCdma.cellIdentityCdma.base.systemId,
+ cellInfoCdma.cellIdentityCdma.base.baseStationId,
+ cellInfoCdma.cellIdentityCdma.base.longitude,
+ cellInfoCdma.cellIdentityCdma.base.latitude,
+ cellInfoCdma.cellIdentityCdma.operatorNames.alphaLong,
+ cellInfoCdma.cellIdentityCdma.operatorNames.alphaShort,
+ cellInfoCdma.signalStrengthCdma.dbm,
+ cellInfoCdma.signalStrengthCdma.ecio,
+ cellInfoCdma.signalStrengthEvdo.dbm,
+ cellInfoCdma.signalStrengthEvdo.ecio,
+ cellInfoCdma.signalStrengthEvdo.signalNoiseRatio);
+ break;
+ }
+
+ case CellInfoType.LTE: {
+ android.hardware.radio.V1_2.CellInfoLte cellInfoLte = record.lte.get(0);
+ writeToParcelForLte(
+ p,
+ cellInfoLte.cellIdentityLte.base.ci,
+ cellInfoLte.cellIdentityLte.base.pci,
+ cellInfoLte.cellIdentityLte.base.tac,
+ cellInfoLte.cellIdentityLte.base.earfcn,
+ cellInfoLte.cellIdentityLte.base.mcc,
+ cellInfoLte.cellIdentityLte.base.mnc,
+ cellInfoLte.cellIdentityLte.operatorNames.alphaLong,
+ cellInfoLte.cellIdentityLte.operatorNames.alphaShort,
+ cellInfoLte.signalStrengthLte.signalStrength,
+ cellInfoLte.signalStrengthLte.rsrp,
+ cellInfoLte.signalStrengthLte.rsrq,
+ cellInfoLte.signalStrengthLte.rssnr,
+ cellInfoLte.signalStrengthLte.cqi,
+ cellInfoLte.signalStrengthLte.timingAdvance);
+ break;
+ }
+
+ case CellInfoType.WCDMA: {
+ android.hardware.radio.V1_2.CellInfoWcdma cellInfoWcdma = record.wcdma.get(0);
+ writeToParcelForWcdma(
+ p,
+ cellInfoWcdma.cellIdentityWcdma.base.lac,
+ cellInfoWcdma.cellIdentityWcdma.base.cid,
+ cellInfoWcdma.cellIdentityWcdma.base.psc,
+ cellInfoWcdma.cellIdentityWcdma.base.uarfcn,
+ cellInfoWcdma.cellIdentityWcdma.base.mcc,
+ cellInfoWcdma.cellIdentityWcdma.base.mnc,
+ cellInfoWcdma.cellIdentityWcdma.operatorNames.alphaLong,
+ cellInfoWcdma.cellIdentityWcdma.operatorNames.alphaShort,
+ cellInfoWcdma.signalStrengthWcdma.signalStrength,
+ cellInfoWcdma.signalStrengthWcdma.bitErrorRate);
break;
}
diff --git a/com/android/internal/telephony/RadioIndication.java b/com/android/internal/telephony/RadioIndication.java
index a7d2418b..bcae450f 100644
--- a/com/android/internal/telephony/RadioIndication.java
+++ b/com/android/internal/telephony/RadioIndication.java
@@ -640,6 +640,12 @@ public class RadioIndication extends IRadioIndication.Stub {
responseCellInfos(indicationType, result);
}
+ /** Incremental network scan results with HAL V1_2 */
+ public void networkScanResult_1_2(int indicationType,
+ android.hardware.radio.V1_2.NetworkScanResult result) {
+ responseCellInfos_1_2(indicationType, result);
+ }
+
public void imsNetworkStateChanged(int indicationType) {
mRil.processIndication(indicationType);
@@ -842,4 +848,15 @@ public class RadioIndication extends IRadioIndication.Stub {
if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
}
+
+ private void responseCellInfos_1_2(int indicationType,
+ android.hardware.radio.V1_2.NetworkScanResult result) {
+ mRil.processIndication(indicationType);
+
+ NetworkScanResult nsr = null;
+ ArrayList<CellInfo> infos = RIL.convertHalCellInfoList_1_2(result.networkInfos);
+ nsr = new NetworkScanResult(result.status, result.error, infos);
+ if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
+ mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
+ }
}
diff --git a/com/android/internal/telephony/RadioResponse.java b/com/android/internal/telephony/RadioResponse.java
index 3990d241..84c1d573 100644
--- a/com/android/internal/telephony/RadioResponse.java
+++ b/com/android/internal/telephony/RadioResponse.java
@@ -946,6 +946,16 @@ public class RadioResponse extends IRadioResponse.Stub {
/**
* @param responseInfo Response info struct containing response type, serial no. and error
+ * @param cellInfo List of current cell information known to radio
+ */
+ public void getCellInfoListResponse_1_2(
+ RadioResponseInfo responseInfo,
+ ArrayList<android.hardware.radio.V1_2.CellInfo> cellInfo) {
+ responseCellInfoList_1_2(responseInfo, cellInfo);
+ }
+
+ /**
+ * @param responseInfo Response info struct containing response type, serial no. and error
*/
public void setCellInfoListRateResponse(RadioResponseInfo responseInfo) {
responseVoid(responseInfo);
@@ -1681,6 +1691,20 @@ public class RadioResponse extends IRadioResponse.Stub {
}
}
+ private void responseCellInfoList_1_2(
+ RadioResponseInfo responseInfo,
+ ArrayList<android.hardware.radio.V1_2.CellInfo> cellInfo) {
+ RILRequest rr = mRil.processResponse(responseInfo);
+
+ if (rr != null) {
+ ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_2(cellInfo);
+ if (responseInfo.error == RadioError.NONE) {
+ sendMessageResponse(rr.mResult, ret);
+ }
+ mRil.processResponseDone(rr, responseInfo, ret);
+ }
+ }
+
private void responseActivityData(RadioResponseInfo responseInfo,
ActivityStatsInfo activityInfo) {
RILRequest rr = mRil.processResponse(responseInfo);
diff --git a/com/android/internal/telephony/ServiceStateTracker.java b/com/android/internal/telephony/ServiceStateTracker.java
index 0f4c9e4a..a9523bbd 100644
--- a/com/android/internal/telephony/ServiceStateTracker.java
+++ b/com/android/internal/telephony/ServiceStateTracker.java
@@ -210,6 +210,7 @@ public class ServiceStateTracker extends Handler {
protected static final int EVENT_RADIO_POWER_FROM_CARRIER = 51;
protected static final int EVENT_SIM_NOT_INSERTED = 52;
protected static final int EVENT_IMS_SERVICE_STATE_CHANGED = 53;
+ protected static final int EVENT_RADIO_POWER_OFF_DONE = 54;
protected static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
@@ -1130,6 +1131,16 @@ public class ServiceStateTracker extends Handler {
}
break;
+ case EVENT_RADIO_POWER_OFF_DONE:
+ if (DBG) log("EVENT_RADIO_POWER_OFF_DONE");
+ if (mDeviceShuttingDown && mCi.getRadioState().isAvailable()) {
+ // during shutdown the modem may not send radio state changed event
+ // as a result of radio power request
+ // Hence, issuing shut down regardless of radio power response
+ mCi.requestShutdown(null);
+ }
+ break;
+
// GSM
case EVENT_SIM_READY:
// Reset the mPreviousSubId so we treat a SIM power bounce
@@ -4403,7 +4414,7 @@ public class ServiceStateTracker extends Handler {
mPhone.mCT.mForegroundCall.hangupIfAlive();
}
- mCi.setRadioPower(false, null);
+ mCi.setRadioPower(false, obtainMessage(EVENT_RADIO_POWER_OFF_DONE));
}
diff --git a/com/android/internal/telephony/SubscriptionInfoUpdater.java b/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 1a2e09c7..983c436f 100644
--- a/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -273,7 +273,8 @@ public class SubscriptionInfoUpdater extends Handler {
if (ar.exception == null) {
if (ar.result != null) {
byte[] data = (byte[])ar.result;
- mIccId[slotId] = IccUtils.bchToString(data, 0, data.length);
+ mIccId[slotId] = stripIccIdSuffix(
+ IccUtils.bchToString(data, 0, data.length));
} else {
logd("Null ar");
mIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
@@ -399,11 +400,11 @@ public class SubscriptionInfoUpdater extends Handler {
logd("handleSimLoaded: IccRecords null");
return;
}
- if (records.getFullIccId() == null) {
+ if (stripIccIdSuffix(records.getFullIccId()) == null) {
logd("onRecieve: IccID null");
return;
}
- mIccId[slotId] = records.getFullIccId();
+ mIccId[slotId] = stripIccIdSuffix(records.getFullIccId());
if (isAllIccIdQueryDone()) {
updateSubscriptionInfoByIccId();
@@ -820,6 +821,15 @@ public class SubscriptionInfoUpdater extends Handler {
IntentBroadcaster.getInstance().broadcastStickyIntent(i, slotId);
}
+ // Remove trailing F's from full hexadecimal IccId, as they should be considered padding
+ private String stripIccIdSuffix(String hexIccId) {
+ if (hexIccId == null) {
+ return null;
+ } else {
+ return hexIccId.replaceAll("(?i)f*$", "");
+ }
+ }
+
public void dispose() {
logd("[dispose]");
mContext.unregisterReceiver(sReceiver);
diff --git a/com/android/internal/telephony/TelephonyComponentFactory.java b/com/android/internal/telephony/TelephonyComponentFactory.java
index 193d29e1..82a08238 100644
--- a/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -77,6 +77,10 @@ public class TelephonyComponentFactory {
return new CarrierActionAgent(phone);
}
+ public CarrierIdentifier makeCarrierIdentifier(Phone phone) {
+ return new CarrierIdentifier(phone);
+ }
+
public IccPhoneBookInterfaceManager makeIccPhoneBookInterfaceManager(Phone phone) {
return new IccPhoneBookInterfaceManager(phone);
}
diff --git a/com/android/internal/telephony/cat/ComprehensionTlv.java b/com/android/internal/telephony/cat/ComprehensionTlv.java
index e2522a4f..d4ad532b 100644
--- a/com/android/internal/telephony/cat/ComprehensionTlv.java
+++ b/com/android/internal/telephony/cat/ComprehensionTlv.java
@@ -29,7 +29,7 @@ import java.util.List;
*
* {@hide}
*/
-class ComprehensionTlv {
+public class ComprehensionTlv {
private static final String LOG_TAG = "ComprehensionTlv";
private int mTag;
private boolean mCr;
diff --git a/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java b/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
index 5f2e561b..d27a7581 100644
--- a/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
+++ b/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
@@ -18,6 +18,7 @@ package com.android.internal.telephony.cdma.sms;
import android.util.SparseBooleanArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.SmsAddress;
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.util.HexDump;
@@ -113,8 +114,8 @@ public class CdmaSmsAddress extends SmsAddress {
* share code and logic with GSM. Also, gather all DTMF/BCD
* processing code in one place.
*/
-
- private static byte[] parseToDtmf(String address) {
+ @VisibleForTesting
+ public static byte[] parseToDtmf(String address) {
int digits = address.length();
byte[] result = new byte[digits];
for (int i = 0; i < digits; i++) {
@@ -196,33 +197,46 @@ public class CdmaSmsAddress extends SmsAddress {
public static CdmaSmsAddress parse(String address) {
CdmaSmsAddress addr = new CdmaSmsAddress();
addr.address = address;
- addr.ton = CdmaSmsAddress.TON_UNKNOWN;
- byte[] origBytes = null;
+ addr.ton = TON_UNKNOWN;
+ addr.digitMode = DIGIT_MODE_4BIT_DTMF;
+ addr.numberPlan = NUMBERING_PLAN_UNKNOWN;
+ addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
+
+ byte[] origBytes;
String filteredAddr = filterNumericSugar(address);
- if (filteredAddr != null) {
- origBytes = parseToDtmf(filteredAddr);
- }
- if (origBytes != null) {
- addr.digitMode = DIGIT_MODE_4BIT_DTMF;
- addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
- if (address.indexOf('+') != -1) {
- addr.ton = TON_INTERNATIONAL_OR_IP;
- }
- } else {
- filteredAddr = filterWhitespace(address);
- origBytes = UserData.stringToAscii(filteredAddr);
- if (origBytes == null) {
- return null;
- }
+ if (address.contains("+") || filteredAddr == null) {
+ // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters
+ // NUMBER_MODE should set to 1 for network address and email address.
addr.digitMode = DIGIT_MODE_8BIT_CHAR;
addr.numberMode = NUMBER_MODE_DATA_NETWORK;
- if (address.indexOf('@') != -1) {
+ filteredAddr = filterWhitespace(address);
+
+ if (address.contains("@")) {
+ // This is an email address
addr.ton = TON_NATIONAL_OR_EMAIL;
+ } else if (address.contains("+") && filterNumericSugar(address) != null) {
+ // This is an international number
+ // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters
+ // digit mode is set to 1 and number mode is set to 0, type of number should set
+ // to the value correspond to the value in 3GPP2 C.S005-D, table2.7.1.3.2.4-2
+ addr.ton = TON_INTERNATIONAL_OR_IP;
+ addr.numberPlan = NUMBERING_PLAN_ISDN_TELEPHONY;
+ addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
+ filteredAddr = filterNumericSugar(address);
}
+
+ origBytes = UserData.stringToAscii(filteredAddr);
+ } else {
+ // The address is not an international number and it only contains digit and *#
+ origBytes = parseToDtmf(filteredAddr);
+ }
+
+ if (origBytes == null) {
+ return null;
}
+
addr.origBytes = origBytes;
addr.numberOfDigits = origBytes.length;
return addr;
}
-
}
diff --git a/com/android/internal/telephony/dataconnection/ApnSetting.java b/com/android/internal/telephony/dataconnection/ApnSetting.java
index ce8318d3..0eeed6a0 100644
--- a/com/android/internal/telephony/dataconnection/ApnSetting.java
+++ b/com/android/internal/telephony/dataconnection/ApnSetting.java
@@ -23,6 +23,7 @@ import android.telephony.CarrierConfigManager;
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.text.TextUtils;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
@@ -48,6 +49,7 @@ public class ApnSetting {
static final String V2_FORMAT_REGEX = "^\\[ApnSettingV2\\]\\s*";
static final String V3_FORMAT_REGEX = "^\\[ApnSettingV3\\]\\s*";
+ static final String TAG = "ApnSetting";
public final String carrier;
public final String apn;
@@ -100,6 +102,7 @@ public class ApnSetting {
* "spn": Service provider name.
* "imsi": IMSI.
* "gid": Group identifier level 1.
+ * "iccid": ICCID
*/
public final String mvnoType;
/**
@@ -107,6 +110,7 @@ public class ApnSetting {
* "spn": A MOBILE, BEN NL
* "imsi": 302720x94, 2060188
* "gid": 4E, 33
+ * "iccid": 898603 etc.
*/
public final String mvnoMatchData;
@@ -362,6 +366,17 @@ public class ApnSetting {
return false;
}
+ private static boolean iccidMatches(String mvnoData, String iccId) {
+ String[] mvnoIccidList = mvnoData.split(",");
+ for (String mvnoIccid : mvnoIccidList) {
+ if (iccId.startsWith(mvnoIccid)) {
+ Log.d(TAG, "mvno icc id match found");
+ return true;
+ }
+ }
+ return false;
+ }
+
private static boolean imsiMatches(String imsiDB, String imsiSIM) {
// Note: imsiDB value has digit number or 'x' character for seperating USIM information
// for MVNO operator. And then digit number is matched at same order and 'x' character
@@ -404,7 +419,13 @@ public class ApnSetting {
gid1.substring(0, mvno_match_data_length).equalsIgnoreCase(mvnoMatchData)) {
return true;
}
+ } else if (mvnoType.equalsIgnoreCase("iccid")) {
+ String iccId = r.getIccId();
+ if ((iccId != null) && iccidMatches(mvnoMatchData, iccId)) {
+ return true;
+ }
}
+
return false;
}
diff --git a/com/android/internal/telephony/dataconnection/DataCallResponse.java b/com/android/internal/telephony/dataconnection/DataCallResponse.java
index bc02b979..5196bcee 100644
--- a/com/android/internal/telephony/dataconnection/DataCallResponse.java
+++ b/com/android/internal/telephony/dataconnection/DataCallResponse.java
@@ -17,18 +17,9 @@
package com.android.internal.telephony.dataconnection;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.NetworkUtils;
-import android.net.RouteInfo;
import android.os.SystemProperties;
-import android.telephony.Rlog;
import android.text.TextUtils;
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
/**
* This is RIL_Data_Call_Response_v5 from ril.h
*/
@@ -44,34 +35,10 @@ public class DataCallResponse {
public final String ifname;
public final String [] addresses;
public final String [] dnses;
- // TODO: Change this to final if possible.
- public String[] gateways;
+ public final String[] gateways;
public final String [] pcscf;
public final int mtu;
- /**
- * Class returned by onSetupConnectionCompleted.
- */
- public enum SetupResult {
- SUCCESS,
- ERR_BadCommand,
- ERR_UnacceptableParameter,
- ERR_GetLastErrorFromRil,
- ERR_Stale,
- ERR_RilError;
-
- public DcFailCause mFailCause;
-
- SetupResult() {
- mFailCause = DcFailCause.fromInt(0);
- }
-
- @Override
- public String toString() {
- return name() + " SetupResult.mFailCause=" + mFailCause;
- }
- }
-
public DataCallResponse(int status, int suggestedRetryTime, int cid, int active, String type,
String ifname, String addresses, String dnses, String gateways,
String pcscf, int mtu) {
@@ -86,7 +53,22 @@ public class DataCallResponse {
}
this.addresses = TextUtils.isEmpty(addresses) ? new String[0] : addresses.split(" ");
this.dnses = TextUtils.isEmpty(dnses) ? new String[0] : dnses.split(" ");
- this.gateways = TextUtils.isEmpty(gateways) ? new String[0] : gateways.split(" ");
+
+ String[] myGateways = TextUtils.isEmpty(gateways)
+ ? new String[0] : gateways.split(" ");
+
+ // set gateways
+ if (myGateways.length == 0) {
+ String propertyPrefix = "net." + this.ifname + ".";
+ String sysGateways = SystemProperties.get(propertyPrefix + "gw");
+ if (sysGateways != null) {
+ myGateways = sysGateways.split(" ");
+ } else {
+ myGateways = new String[0];
+ }
+ }
+ this.gateways = myGateways;
+
this.pcscf = TextUtils.isEmpty(pcscf) ? new String[0] : pcscf.split(" ");
this.mtu = mtu;
}
@@ -129,147 +111,4 @@ public class DataCallResponse {
sb.append("]}");
return sb.toString();
}
-
- public SetupResult setLinkProperties(LinkProperties linkProperties,
- boolean okToUseSystemPropertyDns) {
- SetupResult result;
-
- // Start with clean network properties and if we have
- // a failure we'll clear again at the bottom of this code.
- if (linkProperties == null)
- linkProperties = new LinkProperties();
- else
- linkProperties.clear();
-
- if (status == DcFailCause.NONE.getErrorCode()) {
- String propertyPrefix = "net." + ifname + ".";
-
- try {
- // set interface name
- linkProperties.setInterfaceName(ifname);
-
- // set link addresses
- if (addresses != null && addresses.length > 0) {
- for (String addr : addresses) {
- addr = addr.trim();
- if (addr.isEmpty()) continue;
- LinkAddress la;
- int addrPrefixLen;
-
- String [] ap = addr.split("/");
- if (ap.length == 2) {
- addr = ap[0];
- addrPrefixLen = Integer.parseInt(ap[1]);
- } else {
- addrPrefixLen = 0;
- }
- InetAddress ia;
- try {
- ia = NetworkUtils.numericToInetAddress(addr);
- } catch (IllegalArgumentException e) {
- throw new UnknownHostException("Non-numeric ip addr=" + addr);
- }
- if (! ia.isAnyLocalAddress()) {
- if (addrPrefixLen == 0) {
- // Assume point to point
- addrPrefixLen = (ia instanceof Inet4Address) ? 32 : 128;
- }
- if (DBG) Rlog.d(LOG_TAG, "addr/pl=" + addr + "/" + addrPrefixLen);
- try {
- la = new LinkAddress(ia, addrPrefixLen);
- } catch (IllegalArgumentException e) {
- throw new UnknownHostException("Bad parameter for LinkAddress, ia="
- + ia.getHostAddress() + "/" + addrPrefixLen);
- }
-
- linkProperties.addLinkAddress(la);
- }
- }
- } else {
- throw new UnknownHostException("no address for ifname=" + ifname);
- }
-
- // set dns servers
- if (dnses != null && dnses.length > 0) {
- for (String addr : dnses) {
- addr = addr.trim();
- if (addr.isEmpty()) continue;
- InetAddress ia;
- try {
- ia = NetworkUtils.numericToInetAddress(addr);
- } catch (IllegalArgumentException e) {
- throw new UnknownHostException("Non-numeric dns addr=" + addr);
- }
- if (! ia.isAnyLocalAddress()) {
- linkProperties.addDnsServer(ia);
- }
- }
- } else if (okToUseSystemPropertyDns){
- String dnsServers[] = new String[2];
- dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
- dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
- for (String dnsAddr : dnsServers) {
- dnsAddr = dnsAddr.trim();
- if (dnsAddr.isEmpty()) continue;
- InetAddress ia;
- try {
- ia = NetworkUtils.numericToInetAddress(dnsAddr);
- } catch (IllegalArgumentException e) {
- throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr);
- }
- if (! ia.isAnyLocalAddress()) {
- linkProperties.addDnsServer(ia);
- }
- }
- } else {
- throw new UnknownHostException("Empty dns response and no system default dns");
- }
-
- // set gateways
- if ((gateways == null) || (gateways.length == 0)) {
- String sysGateways = SystemProperties.get(propertyPrefix + "gw");
- if (sysGateways != null) {
- gateways = sysGateways.split(" ");
- } else {
- gateways = new String[0];
- }
- }
- for (String addr : gateways) {
- addr = addr.trim();
- if (addr.isEmpty()) continue;
- InetAddress ia;
- try {
- ia = NetworkUtils.numericToInetAddress(addr);
- } catch (IllegalArgumentException e) {
- throw new UnknownHostException("Non-numeric gateway addr=" + addr);
- }
- // Allow 0.0.0.0 or :: as a gateway; this indicates a point-to-point interface.
- linkProperties.addRoute(new RouteInfo(ia));
- }
-
- // set interface MTU
- // this may clobber the setting read from the APN db, but that's ok
- linkProperties.setMtu(mtu);
-
- result = SetupResult.SUCCESS;
- } catch (UnknownHostException e) {
- Rlog.d(LOG_TAG, "setLinkProperties: UnknownHostException " + e);
- e.printStackTrace();
- result = SetupResult.ERR_UnacceptableParameter;
- }
- } else {
- result = SetupResult.ERR_RilError;
- }
-
- // An error occurred so clear properties
- if (result != SetupResult.SUCCESS) {
- if(DBG) {
- Rlog.d(LOG_TAG, "setLinkProperties: error clearing LinkProperties " +
- "status=" + status + " result=" + result);
- }
- linkProperties.clear();
- }
-
- return result;
- }
}
diff --git a/com/android/internal/telephony/dataconnection/DataConnection.java b/com/android/internal/telephony/dataconnection/DataConnection.java
index 9e4b29cd..3dbcc3d5 100644
--- a/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -19,12 +19,15 @@ package com.android.internal.telephony.dataconnection;
import android.app.PendingIntent;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkMisc;
+import android.net.NetworkUtils;
import android.net.ProxyInfo;
+import android.net.RouteInfo;
import android.net.StringNetworkSpecifier;
import android.os.AsyncResult;
import android.os.Looper;
@@ -34,6 +37,7 @@ import android.os.SystemProperties;
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
+import android.telephony.data.DataProfile;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Pair;
@@ -59,7 +63,9 @@ import com.android.internal.util.StateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.net.Inet4Address;
import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -283,7 +289,7 @@ public class DataConnection extends StateMachine {
}
public static class UpdateLinkPropertyResult {
- public DataCallResponse.SetupResult setupResult = DataCallResponse.SetupResult.SUCCESS;
+ public SetupResult setupResult = SetupResult.SUCCESS;
public LinkProperties oldLp;
public LinkProperties newLp;
public UpdateLinkPropertyResult(LinkProperties curLp) {
@@ -292,6 +298,29 @@ public class DataConnection extends StateMachine {
}
}
+ /**
+ * Class returned by onSetupConnectionCompleted.
+ */
+ public enum SetupResult {
+ SUCCESS,
+ ERR_BadCommand,
+ ERR_UnacceptableParameter,
+ ERR_GetLastErrorFromRil,
+ ERR_Stale,
+ ERR_RilError;
+
+ public DcFailCause mFailCause;
+
+ SetupResult() {
+ mFailCause = DcFailCause.fromInt(0);
+ }
+
+ @Override
+ public String toString() {
+ return name() + " SetupResult.mFailCause=" + mFailCause;
+ }
+ }
+
public boolean isIpv4Connected() {
boolean ret = false;
Collection <InetAddress> addresses = mLinkProperties.getAddresses();
@@ -331,12 +360,12 @@ public class DataConnection extends StateMachine {
if (newState == null) return result;
- DataCallResponse.SetupResult setupResult;
+ SetupResult setupResult;
result.newLp = new LinkProperties();
// set link properties based on data call response
result.setupResult = setLinkProperties(newState, result.newLp);
- if (result.setupResult != DataCallResponse.SetupResult.SUCCESS) {
+ if (result.setupResult != SetupResult.SUCCESS) {
if (DBG) log("updateLinkProperty failed : " + result.setupResult);
return result;
}
@@ -465,7 +494,7 @@ public class DataConnection extends StateMachine {
Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
msg.obj = cp;
- DataProfile dp = new DataProfile(mApnSetting, cp.mProfileId);
+ DataProfile dp = DcTracker.createDataProfile(mApnSetting, cp.mProfileId);
// We need to use the actual modem roaming state instead of the framework roaming state
// here. This flag is only passed down to ril_service for picking the correct protocol (for
@@ -665,16 +694,16 @@ public class DataConnection extends StateMachine {
* @param ar is the result
* @return SetupResult.
*/
- private DataCallResponse.SetupResult onSetupConnectionCompleted(AsyncResult ar) {
+ private SetupResult onSetupConnectionCompleted(AsyncResult ar) {
DataCallResponse response = (DataCallResponse) ar.result;
ConnectionParams cp = (ConnectionParams) ar.userObj;
- DataCallResponse.SetupResult result;
+ SetupResult result;
if (cp.mTag != mTag) {
if (DBG) {
log("onSetupConnectionCompleted stale cp.tag=" + cp.mTag + ", mtag=" + mTag);
}
- result = DataCallResponse.SetupResult.ERR_Stale;
+ result = SetupResult.ERR_Stale;
} else if (ar.exception != null) {
if (DBG) {
log("onSetupConnectionCompleted failed, ar.exception=" + ar.exception +
@@ -684,14 +713,14 @@ public class DataConnection extends StateMachine {
if (ar.exception instanceof CommandException
&& ((CommandException) (ar.exception)).getCommandError()
== CommandException.Error.RADIO_NOT_AVAILABLE) {
- result = DataCallResponse.SetupResult.ERR_BadCommand;
+ result = SetupResult.ERR_BadCommand;
result.mFailCause = DcFailCause.RADIO_NOT_AVAILABLE;
} else {
- result = DataCallResponse.SetupResult.ERR_RilError;
+ result = SetupResult.ERR_RilError;
result.mFailCause = DcFailCause.fromInt(response.status);
}
} else if (response.status != 0) {
- result = DataCallResponse.SetupResult.ERR_RilError;
+ result = SetupResult.ERR_RilError;
result.mFailCause = DcFailCause.fromInt(response.status);
} else {
if (DBG) log("onSetupConnectionCompleted received successful DataCallResponse");
@@ -995,18 +1024,139 @@ public class DataConnection extends StateMachine {
return InetAddress.isNumeric(address);
}
- private DataCallResponse.SetupResult setLinkProperties(DataCallResponse response,
- LinkProperties lp) {
+ private SetupResult setLinkProperties(DataCallResponse response,
+ LinkProperties linkProperties) {
// Check if system property dns usable
- boolean okToUseSystemPropertyDns = false;
String propertyPrefix = "net." + response.ifname + ".";
String dnsServers[] = new String[2];
dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
- okToUseSystemPropertyDns = isDnsOk(dnsServers);
+ boolean okToUseSystemPropertyDns = isDnsOk(dnsServers);
+
+ SetupResult result;
+
+ // Start with clean network properties and if we have
+ // a failure we'll clear again at the bottom of this code.
+ linkProperties.clear();
+
+ if (response.status == DcFailCause.NONE.getErrorCode()) {
+ try {
+ // set interface name
+ linkProperties.setInterfaceName(response.ifname);
+
+ // set link addresses
+ if (response.addresses != null && response.addresses.length > 0) {
+ for (String addr : response.addresses) {
+ addr = addr.trim();
+ if (addr.isEmpty()) continue;
+ LinkAddress la;
+ int addrPrefixLen;
+
+ String [] ap = addr.split("/");
+ if (ap.length == 2) {
+ addr = ap[0];
+ addrPrefixLen = Integer.parseInt(ap[1]);
+ } else {
+ addrPrefixLen = 0;
+ }
+ InetAddress ia;
+ try {
+ ia = NetworkUtils.numericToInetAddress(addr);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Non-numeric ip addr=" + addr);
+ }
+ if (!ia.isAnyLocalAddress()) {
+ if (addrPrefixLen == 0) {
+ // Assume point to point
+ addrPrefixLen = (ia instanceof Inet4Address) ? 32 : 128;
+ }
+ if (DBG) log("addr/pl=" + addr + "/" + addrPrefixLen);
+ try {
+ la = new LinkAddress(ia, addrPrefixLen);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Bad parameter for LinkAddress, ia="
+ + ia.getHostAddress() + "/" + addrPrefixLen);
+ }
- // set link properties based on data call response
- return response.setLinkProperties(lp, okToUseSystemPropertyDns);
+ linkProperties.addLinkAddress(la);
+ }
+ }
+ } else {
+ throw new UnknownHostException("no address for ifname=" + response.ifname);
+ }
+
+ // set dns servers
+ if (response.dnses != null && response.dnses.length > 0) {
+ for (String addr : response.dnses) {
+ addr = addr.trim();
+ if (addr.isEmpty()) continue;
+ InetAddress ia;
+ try {
+ ia = NetworkUtils.numericToInetAddress(addr);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Non-numeric dns addr=" + addr);
+ }
+ if (!ia.isAnyLocalAddress()) {
+ linkProperties.addDnsServer(ia);
+ }
+ }
+ } else if (okToUseSystemPropertyDns) {
+ dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
+ dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
+ for (String dnsAddr : dnsServers) {
+ dnsAddr = dnsAddr.trim();
+ if (dnsAddr.isEmpty()) continue;
+ InetAddress ia;
+ try {
+ ia = NetworkUtils.numericToInetAddress(dnsAddr);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr);
+ }
+ if (!ia.isAnyLocalAddress()) {
+ linkProperties.addDnsServer(ia);
+ }
+ }
+ } else {
+ throw new UnknownHostException("Empty dns response and no system default dns");
+ }
+
+ for (String addr : response.gateways) {
+ addr = addr.trim();
+ if (addr.isEmpty()) continue;
+ InetAddress ia;
+ try {
+ ia = NetworkUtils.numericToInetAddress(addr);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Non-numeric gateway addr=" + addr);
+ }
+ // Allow 0.0.0.0 or :: as a gateway; this indicates a point-to-point interface.
+ linkProperties.addRoute(new RouteInfo(ia));
+ }
+
+ // set interface MTU
+ // this may clobber the setting read from the APN db, but that's ok
+ linkProperties.setMtu(response.mtu);
+
+ result = SetupResult.SUCCESS;
+ } catch (UnknownHostException e) {
+ log("setLinkProperties: UnknownHostException " + e);
+ e.printStackTrace();
+ result = SetupResult.ERR_UnacceptableParameter;
+ }
+ } else {
+ result = SetupResult.ERR_RilError;
+ }
+
+ // An error occurred so clear properties
+ if (result != SetupResult.SUCCESS) {
+ if (DBG) {
+ log("setLinkProperties: error clearing LinkProperties status=" + response.status
+ + " result=" + result);
+ }
+ linkProperties.clear();
+ }
+
+ return result;
}
/**
@@ -1421,8 +1571,8 @@ public class DataConnection extends StateMachine {
ar = (AsyncResult) msg.obj;
cp = (ConnectionParams) ar.userObj;
- DataCallResponse.SetupResult result = onSetupConnectionCompleted(ar);
- if (result != DataCallResponse.SetupResult.ERR_Stale) {
+ SetupResult result = onSetupConnectionCompleted(ar);
+ if (result != SetupResult.ERR_Stale) {
if (mConnectionParams != cp) {
loge("DcActivatingState: WEIRD mConnectionsParams:"+ mConnectionParams
+ " != cp:" + cp);
diff --git a/com/android/internal/telephony/dataconnection/DataProfile.java b/com/android/internal/telephony/dataconnection/DataProfile.java
deleted file mode 100644
index 48a8107d..00000000
--- a/com/android/internal/telephony/dataconnection/DataProfile.java
+++ /dev/null
@@ -1,133 +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.internal.telephony.dataconnection;
-
-import android.telephony.ServiceState;
-import android.text.TextUtils;
-
-import com.android.internal.telephony.RILConstants;
-
-public class DataProfile {
-
- static final int TYPE_COMMON = 0;
- static final int TYPE_3GPP = 1;
- static final int TYPE_3GPP2 = 2;
-
- //id of the data profile
- public final int profileId;
- //the APN to connect to
- public final String apn;
- //one of the PDP_type values in TS 27.007 section 10.1.1.
- //For example, "IP", "IPV6", "IPV4V6", or "PPP".
- public final String protocol;
- //authentication protocol used for this PDP context
- //(None: 0, PAP: 1, CHAP: 2, PAP&CHAP: 3)
- public final int authType;
- //the username for APN, or NULL
- public final String user;
- //the password for APN, or NULL
- public final String password;
- //the profile type, TYPE_COMMON, TYPE_3GPP, TYPE_3GPP2
- public final int type;
- //the period in seconds to limit the maximum connections
- public final int maxConnsTime;
- //the maximum connections during maxConnsTime
- public final int maxConns;
- //the required wait time in seconds after a successful UE initiated
- //disconnect of a given PDN connection before the device can send
- //a new PDN connection request for that given PDN
- public final int waitTime;
- //true to enable the profile, false to disable
- public final boolean enabled;
- //supported APN types bitmap. See RIL_ApnTypes for the value of each bit.
- public final int supportedApnTypesBitmap;
- //one of the PDP_type values in TS 27.007 section 10.1.1 used on roaming network.
- //For example, "IP", "IPV6", "IPV4V6", or "PPP".
- public final String roamingProtocol;
- //The bearer bitmap. See RIL_RadioAccessFamily for the value of each bit.
- public final int bearerBitmap;
- //maximum transmission unit (MTU) size in bytes
- public final int mtu;
- //the MVNO type: possible values are "imsi", "gid", "spn"
- public final String mvnoType;
- //MVNO match data. For example, SPN: A MOBILE, BEN NL, ...
- //IMSI: 302720x94, 2060188, ...
- //GID: 4E, 33, ...
- public final String mvnoMatchData;
- //indicating the data profile was sent to the modem through setDataProfile earlier.
- public final boolean modemCognitive;
-
- DataProfile(int profileId, String apn, String protocol, int authType,
- String user, String password, int type, int maxConnsTime, int maxConns,
- int waitTime, boolean enabled, int supportedApnTypesBitmap, String roamingProtocol,
- int bearerBitmap, int mtu, String mvnoType, String mvnoMatchData,
- boolean modemCognitive) {
-
- this.profileId = profileId;
- this.apn = apn;
- this.protocol = protocol;
- if (authType == -1) {
- authType = TextUtils.isEmpty(user) ? RILConstants.SETUP_DATA_AUTH_NONE
- : RILConstants.SETUP_DATA_AUTH_PAP_CHAP;
- }
- this.authType = authType;
- this.user = user;
- this.password = password;
- this.type = type;
- this.maxConnsTime = maxConnsTime;
- this.maxConns = maxConns;
- this.waitTime = waitTime;
- this.enabled = enabled;
-
- this.supportedApnTypesBitmap = supportedApnTypesBitmap;
- this.roamingProtocol = roamingProtocol;
- this.bearerBitmap = bearerBitmap;
- this.mtu = mtu;
- this.mvnoType = mvnoType;
- this.mvnoMatchData = mvnoMatchData;
- this.modemCognitive = modemCognitive;
- }
-
- public DataProfile(ApnSetting apn) {
- this(apn, apn.profileId);
- }
-
- public DataProfile(ApnSetting apn, int profileId) {
- this(profileId, apn.apn, apn.protocol,
- apn.authType, apn.user, apn.password, apn.bearerBitmask == 0
- ? TYPE_COMMON : (ServiceState.bearerBitmapHasCdma(apn.bearerBitmask)
- ? TYPE_3GPP2 : TYPE_3GPP),
- apn.maxConnsTime, apn.maxConns, apn.waitTime, apn.carrierEnabled, apn.typesBitmap,
- apn.roamingProtocol, apn.bearerBitmask, apn.mtu, apn.mvnoType, apn.mvnoMatchData,
- apn.modemCognitive);
- }
-
- @Override
- public String toString() {
- return "DataProfile=" + profileId + "/" + apn + "/" + protocol + "/" + authType
- + "/" + user + "/" + password + "/" + type + "/" + maxConnsTime
- + "/" + maxConns + "/" + waitTime + "/" + enabled + "/" + supportedApnTypesBitmap
- + "/" + roamingProtocol + "/" + bearerBitmap + "/" + mtu + "/" + mvnoType + "/"
- + mvnoMatchData + "/" + modemCognitive;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof DataProfile == false) return false;
- return (o == this || toString().equals(o.toString()));
- }
-}
diff --git a/com/android/internal/telephony/dataconnection/DcTracker.java b/com/android/internal/telephony/dataconnection/DcTracker.java
index fb756cd9..540ec544 100644
--- a/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -65,6 +65,7 @@ import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaCellLocation;
+import android.telephony.data.DataProfile;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
import android.util.EventLog;
@@ -2093,7 +2094,7 @@ public class DcTracker extends Handler {
} else {
if (DBG) log("setInitialAttachApn: X selected Apn=" + initialAttachApnSetting);
- mPhone.mCi.setInitialAttachApn(new DataProfile(initialAttachApnSetting),
+ mPhone.mCi.setInitialAttachApn(createDataProfile(initialAttachApnSetting),
mPhone.getServiceState().getDataRoamingFromRegistration(), null);
}
}
@@ -3281,7 +3282,7 @@ public class DcTracker extends Handler {
ArrayList<DataProfile> dps = new ArrayList<DataProfile>();
for (ApnSetting apn : mAllApnSettings) {
if (apn.modemCognitive) {
- DataProfile dp = new DataProfile(apn);
+ DataProfile dp = createDataProfile(apn);
if (!dps.contains(dp)) {
dps.add(dp);
}
@@ -4800,4 +4801,25 @@ public class DcTracker extends Handler {
}
}
+ private static DataProfile createDataProfile(ApnSetting apn) {
+ return createDataProfile(apn, apn.profileId);
+ }
+
+ @VisibleForTesting
+ public static DataProfile createDataProfile(ApnSetting apn, int profileId) {
+ int profileType;
+ if (apn.bearerBitmask == 0) {
+ profileType = DataProfile.TYPE_COMMON;
+ } else if (ServiceState.bearerBitmapHasCdma(apn.bearerBitmask)) {
+ profileType = DataProfile.TYPE_3GPP2;
+ } else {
+ profileType = DataProfile.TYPE_3GPP;
+ }
+
+ return new DataProfile(profileId, apn.apn, apn.protocol,
+ apn.authType, apn.user, apn.password, profileType,
+ apn.maxConnsTime, apn.maxConns, apn.waitTime, apn.carrierEnabled, apn.typesBitmap,
+ apn.roamingProtocol, apn.bearerBitmask, apn.mtu, apn.mvnoType, apn.mvnoMatchData,
+ apn.modemCognitive);
+ }
}
diff --git a/com/android/internal/telephony/euicc/EuiccController.java b/com/android/internal/telephony/euicc/EuiccController.java
index ac2a0392..dc847187 100644
--- a/com/android/internal/telephony/euicc/EuiccController.java
+++ b/com/android/internal/telephony/euicc/EuiccController.java
@@ -38,6 +38,7 @@ import android.telephony.UiccAccessRule;
import android.telephony.euicc.DownloadableSubscription;
import android.telephony.euicc.EuiccInfo;
import android.telephony.euicc.EuiccManager;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -226,6 +227,7 @@ public class EuiccController extends IEuiccController.Stub {
addResolutionIntent(extrasIntent,
EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
mCallingPackage,
+ false /* confirmationCodeRetried */,
getOperationForDeactivateSim());
break;
default:
@@ -306,6 +308,7 @@ public class EuiccController extends IEuiccController.Stub {
Intent extrasIntent = new Intent();
addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
mCallingPackage,
+ false /* confirmationCodeRetried */,
EuiccOperation.forDownloadNoPrivileges(
mCallingToken, mSubscription, mSwitchAfterDownload,
mCallingPackage));
@@ -354,6 +357,7 @@ public class EuiccController extends IEuiccController.Stub {
Intent extrasIntent = new Intent();
addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
mCallingPackage,
+ false /* confirmationCodeRetried */,
EuiccOperation.forDownloadNoPrivileges(
mCallingToken, subscription, mSwitchAfterDownload,
mCallingPackage));
@@ -407,15 +411,21 @@ public class EuiccController extends IEuiccController.Stub {
addResolutionIntent(extrasIntent,
EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
callingPackage,
+ false /* confirmationCodeRetried */,
EuiccOperation.forDownloadDeactivateSim(
callingToken, subscription, switchAfterDownload,
callingPackage));
break;
case EuiccService.RESULT_NEED_CONFIRMATION_CODE:
resultCode = RESOLVABLE_ERROR;
+ boolean retried = false;
+ if (!TextUtils.isEmpty(subscription.getConfirmationCode())) {
+ retried = true;
+ }
addResolutionIntent(extrasIntent,
EuiccService.ACTION_RESOLVE_CONFIRMATION_CODE,
callingPackage,
+ retried /* confirmationCodeRetried */,
EuiccOperation.forDownloadConfirmationCode(
callingToken, subscription, switchAfterDownload,
callingPackage));
@@ -520,6 +530,7 @@ public class EuiccController extends IEuiccController.Stub {
addResolutionIntent(extrasIntent,
EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
mCallingPackage,
+ false /* confirmationCodeRetried */,
EuiccOperation.forGetDefaultListDeactivateSim(
mCallingToken, mCallingPackage));
break;
@@ -671,6 +682,7 @@ public class EuiccController extends IEuiccController.Stub {
addResolutionIntent(extrasIntent,
EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
callingPackage,
+ false /* confirmationCodeRetried */,
EuiccOperation.forSwitchNoPrivileges(
token, subscriptionId, callingPackage));
sendResult(callbackIntent, RESOLVABLE_ERROR, extrasIntent);
@@ -716,6 +728,7 @@ public class EuiccController extends IEuiccController.Stub {
addResolutionIntent(extrasIntent,
EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
callingPackage,
+ false /* confirmationCodeRetried */,
EuiccOperation.forSwitchDeactivateSim(
callingToken, subscriptionId, callingPackage));
break;
@@ -883,11 +896,13 @@ public class EuiccController extends IEuiccController.Stub {
/** Add a resolution intent to the given extras intent. */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public void addResolutionIntent(Intent extrasIntent, String resolutionAction,
- String callingPackage, EuiccOperation op) {
+ String callingPackage, boolean confirmationCodeRetried, EuiccOperation op) {
Intent intent = new Intent(EuiccManager.ACTION_RESOLVE_ERROR);
intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION,
resolutionAction);
intent.putExtra(EuiccService.EXTRA_RESOLUTION_CALLING_PACKAGE, callingPackage);
+ intent.putExtra(EuiccService.EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED,
+ confirmationCodeRetried);
intent.putExtra(EXTRA_OPERATION, op);
PendingIntent resolutionIntent = PendingIntent.getActivity(
mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_ONE_SHOT);
diff --git a/com/android/internal/telephony/euicc/EuiccOperation.java b/com/android/internal/telephony/euicc/EuiccOperation.java
index 84df52e4..148d9dc1 100644
--- a/com/android/internal/telephony/euicc/EuiccOperation.java
+++ b/com/android/internal/telephony/euicc/EuiccOperation.java
@@ -309,9 +309,7 @@ public class EuiccOperation implements Parcelable {
if (TextUtils.isEmpty(confirmationCode)) {
fail(callbackIntent);
} else {
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
- mDownloadableSubscription.setConfirmationCode(confirmationCode);
- }
+ mDownloadableSubscription.setConfirmationCode(confirmationCode);
EuiccController.get()
.downloadSubscription(
mDownloadableSubscription,
diff --git a/com/android/internal/telephony/gsm/GsmMmiCode.java b/com/android/internal/telephony/gsm/GsmMmiCode.java
index b9a07f9f..3376e2b2 100644
--- a/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -863,6 +863,13 @@ public final class GsmMmiCode extends Handler implements MmiCode {
return mIsSsInfo;
}
+ public static boolean isVoiceUnconditionalForwarding(int reason, int serviceClass) {
+ return (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL)
+ || (reason == CommandsInterface.CF_REASON_ALL))
+ && (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0)
+ || (serviceClass == CommandsInterface.SERVICE_CLASS_NONE)));
+ }
+
/** Process a MMI code or short code...anything that isn't a dialing number */
public void
processCode() throws CallStateException {
@@ -933,12 +940,6 @@ public final class GsmMmiCode extends Handler implements MmiCode {
throw new RuntimeException ("invalid action");
}
- int isSettingUnconditionalVoice =
- (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ||
- (reason == CommandsInterface.CF_REASON_ALL)) &&
- (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) ||
- (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0;
-
int isEnableDesired =
((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
(cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
@@ -947,7 +948,7 @@ public final class GsmMmiCode extends Handler implements MmiCode {
mPhone.mCi.setCallForward(cfAction, reason, serviceClass,
dialingNumber, time, obtainMessage(
EVENT_SET_CFF_COMPLETE,
- isSettingUnconditionalVoice,
+ isVoiceUnconditionalForwarding(reason, serviceClass) ? 1 : 0,
isEnableDesired, this));
}
} else if (isServiceCodeCallBarring(mSc)) {
diff --git a/com/android/internal/telephony/ims/ImsResolver.java b/com/android/internal/telephony/ims/ImsResolver.java
index 2f790b7a..4a649840 100644
--- a/com/android/internal/telephony/ims/ImsResolver.java
+++ b/com/android/internal/telephony/ims/ImsResolver.java
@@ -38,8 +38,9 @@ import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
-import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.PhoneConstants;
@@ -295,21 +296,58 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal
}
/**
- * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS
- * feature or {@link null} if the service is not available. If an ImsServiceController is
- * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for
- * feature updates.
- * @param slotId The SIM slot that we are requesting the {@link IImsServiceController} for.
- * @param feature The IMS Feature we are requesting.
+ * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id or {@link null} if
+ * the service is not available. If an IImsMMTelFeature is available, the
+ * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates.
+ * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
* @param callback Listener that will send updates to ImsManager when there are updates to
- * ImsServiceController.
- * @return {@link IImsServiceController} interface for the feature specified or {@link null} if
- * it is unavailable.
+ * the feature.
+ * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable.
*/
- public IImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
- IImsServiceFeatureListener callback) {
- if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.INVALID
- || feature >= ImsFeature.MAX) {
+ public IImsMMTelFeature getMMTelFeatureAndListen(int slotId,
+ IImsServiceFeatureCallback callback) {
+ ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.MMTEL,
+ callback);
+ return (controller != null) ? controller.getMMTelFeature(slotId) : null;
+ }
+
+ /**
+ * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id for emergency
+ * calling or {@link null} if the service is not available. If an IImsMMTelFeature is
+ * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for
+ * feature updates.
+ * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
+ * @param callback listener that will send updates to ImsManager when there are updates to
+ * the feature.
+ * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable.
+ */
+ public IImsMMTelFeature getEmergencyMMTelFeatureAndListen(int slotId,
+ IImsServiceFeatureCallback callback) {
+ ImsServiceController controller = getImsServiceControllerAndListen(slotId,
+ ImsFeature.EMERGENCY_MMTEL, callback);
+ return (controller != null) ? controller.getEmergencyMMTelFeature(slotId) : null;
+ }
+
+ /**
+ * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id for emergency
+ * calling or {@link null} if the service is not available. If an IImsMMTelFeature is
+ * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for
+ * feature updates.
+ * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
+ * @param callback listener that will send updates to ImsManager when there are updates to
+ * the feature.
+ * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable.
+ */
+ public IImsRcsFeature getRcsFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) {
+ ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.RCS,
+ callback);
+ return (controller != null) ? controller.getRcsFeature(slotId) : null;
+ }
+
+ @VisibleForTesting
+ public ImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
+ IImsServiceFeatureCallback callback) {
+ if (slotId < 0 || slotId >= mNumSlots) {
return null;
}
ImsServiceController controller;
@@ -322,7 +360,7 @@ public class ImsResolver implements ImsServiceController.ImsServiceControllerCal
}
if (controller != null) {
controller.addImsServiceFeatureListener(callback);
- return controller.getImsServiceController();
+ return controller;
}
return null;
}
diff --git a/com/android/internal/telephony/ims/ImsServiceController.java b/com/android/internal/telephony/ims/ImsServiceController.java
index 6fcefbd3..6f31b50a 100644
--- a/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/com/android/internal/telephony/ims/ImsServiceController.java
@@ -24,14 +24,18 @@ import android.content.pm.IPackageManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.IInterface;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.telephony.ims.feature.ImsFeature;
import android.util.Log;
import android.util.Pair;
import com.android.ims.internal.IImsFeatureStatusCallback;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.ExponentialBackoff;
@@ -171,15 +175,54 @@ public class ImsServiceController {
private boolean mIsBinding = false;
// Set of a pair of slotId->feature
private HashSet<Pair<Integer, Integer>> mImsFeatures;
+ // Binder interfaces to the features set in mImsFeatures;
+ private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>();
private IImsServiceController mIImsServiceController;
// Easier for testing.
private IBinder mImsServiceControllerBinder;
private ImsServiceConnection mImsServiceConnection;
private ImsDeathRecipient mImsDeathRecipient;
- private Set<IImsServiceFeatureListener> mImsStatusCallbacks = new HashSet<>();
+ private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = new HashSet<>();
// Only added or removed, never accessed on purpose.
private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>();
+ private class ImsFeatureContainer {
+ public int slotId;
+ public int featureType;
+ private IInterface mBinder;
+
+ ImsFeatureContainer(int slotId, int featureType, IInterface binder) {
+ this.slotId = slotId;
+ this.featureType = featureType;
+ this.mBinder = binder;
+ }
+
+ // Casts the IInterface into the binder class we are looking for.
+ public <T extends IInterface> T resolve(Class<T> className) {
+ return className.cast(mBinder);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ImsFeatureContainer that = (ImsFeatureContainer) o;
+
+ if (slotId != that.slotId) return false;
+ if (featureType != that.featureType) return false;
+ return mBinder != null ? mBinder.equals(that.mBinder) : that.mBinder == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = slotId;
+ result = 31 * result + featureType;
+ result = 31 * result + (mBinder != null ? mBinder.hashCode() : 0);
+ return result;
+ }
+ }
+
/**
* Container class for the IImsFeatureStatusCallback callback implementation. This class is
* never used directly, but we need to keep track of the IImsFeatureStatusCallback
@@ -373,12 +416,56 @@ public class ImsServiceController {
/**
* Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle.
*/
- public void addImsServiceFeatureListener(IImsServiceFeatureListener callback) {
+ public void addImsServiceFeatureListener(IImsServiceFeatureCallback callback) {
synchronized (mLock) {
mImsStatusCallbacks.add(callback);
}
}
+ /**
+ * Return the {@Link MMTelFeature} binder on the slot associated with the slotId.
+ * Used for normal calling.
+ */
+ public IImsMMTelFeature getMMTelFeature(int slotId) {
+ synchronized (mLock) {
+ ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.MMTEL);
+ if (f == null) {
+ Log.w(LOG_TAG, "Requested null MMTelFeature on slot " + slotId);
+ return null;
+ }
+ return f.resolve(IImsMMTelFeature.class);
+ }
+ }
+
+ /**
+ * Return the {@Link MMTelFeature} binder on the slot associated with the slotId.
+ * Used for emergency calling only.
+ */
+ public IImsMMTelFeature getEmergencyMMTelFeature(int slotId) {
+ synchronized (mLock) {
+ ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.EMERGENCY_MMTEL);
+ if (f == null) {
+ Log.w(LOG_TAG, "Requested null Emergency MMTelFeature on slot " + slotId);
+ return null;
+ }
+ return f.resolve(IImsMMTelFeature.class);
+ }
+ }
+
+ /**
+ * Return the {@Link RcsFeature} binder on the slot associated with the slotId.
+ */
+ public IImsRcsFeature getRcsFeature(int slotId) {
+ synchronized (mLock) {
+ ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.RCS);
+ if (f == null) {
+ Log.w(LOG_TAG, "Requested null RcsFeature on slot " + slotId);
+ return null;
+ }
+ return f.resolve(IImsRcsFeature.class);
+ }
+ }
+
private void removeImsServiceFeatureListener() {
synchronized (mLock) {
mImsStatusCallbacks.clear();
@@ -407,9 +494,9 @@ public class ImsServiceController {
private void sendImsFeatureCreatedCallback(int slot, int feature) {
synchronized (mLock) {
- for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator();
+ for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
i.hasNext(); ) {
- IImsServiceFeatureListener callbacks = i.next();
+ IImsServiceFeatureCallback callbacks = i.next();
try {
callbacks.imsFeatureCreated(slot, feature);
} catch (RemoteException e) {
@@ -424,9 +511,9 @@ public class ImsServiceController {
private void sendImsFeatureRemovedCallback(int slot, int feature) {
synchronized (mLock) {
- for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator();
+ for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
i.hasNext(); ) {
- IImsServiceFeatureListener callbacks = i.next();
+ IImsServiceFeatureCallback callbacks = i.next();
try {
callbacks.imsFeatureRemoved(slot, feature);
} catch (RemoteException e) {
@@ -441,9 +528,9 @@ public class ImsServiceController {
private void sendImsFeatureStatusChanged(int slot, int feature, int status) {
synchronized (mLock) {
- for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator();
+ for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
i.hasNext(); ) {
- IImsServiceFeatureListener callbacks = i.next();
+ IImsServiceFeatureCallback callbacks = i.next();
try {
callbacks.imsStatusChanged(slot, feature, status);
} catch (RemoteException e) {
@@ -465,8 +552,8 @@ public class ImsServiceController {
ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.first,
featurePair.second);
mFeatureStatusCallbacks.add(c);
- mIImsServiceController.createImsFeature(featurePair.first, featurePair.second,
- c.getCallback());
+ IInterface f = createImsFeature(featurePair.first, featurePair.second, c.getCallback());
+ addImsFeatureBinder(featurePair.first, featurePair.second, f);
// Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
mCallbacks.imsServiceFeatureCreated(featurePair.first, featurePair.second, this);
// Send callback to ImsServiceProxy to change supported ImsFeatures
@@ -489,6 +576,7 @@ public class ImsServiceController {
}
mIImsServiceController.removeImsFeature(featurePair.first, featurePair.second,
(callbackToRemove != null ? callbackToRemove.getCallback() : null));
+ removeImsFeatureBinder(featurePair.first, featurePair.second);
// Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
mCallbacks.imsServiceFeatureRemoved(featurePair.first, featurePair.second, this);
// Send callback to ImsServiceProxy to change supported ImsFeatures
@@ -498,6 +586,45 @@ public class ImsServiceController {
sendImsFeatureRemovedCallback(featurePair.first, featurePair.second);
}
+ // This method should only be called when already synchronized on mLock;
+ private IInterface createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
+ throws RemoteException {
+ switch (featureType) {
+ case ImsFeature.EMERGENCY_MMTEL: {
+ return mIImsServiceController.createEmergencyMMTelFeature(slotId, c);
+ }
+ case ImsFeature.MMTEL: {
+ return mIImsServiceController.createMMTelFeature(slotId, c);
+ }
+ case ImsFeature.RCS: {
+ return mIImsServiceController.createRcsFeature(slotId, c);
+ }
+ default:
+ return null;
+ }
+ }
+
+ // This method should only be called when synchronized on mLock
+ private void addImsFeatureBinder(int slotId, int featureType, IInterface b) {
+ mImsFeatureBinders.add(new ImsFeatureContainer(slotId, featureType, b));
+ }
+
+ // This method should only be called when synchronized on mLock
+ private void removeImsFeatureBinder(int slotId, int featureType) {
+ ImsFeatureContainer container = mImsFeatureBinders.stream()
+ .filter(f-> (f.slotId == slotId && f.featureType == featureType))
+ .findFirst().orElse(null);
+ if (container != null) {
+ mImsFeatureBinders.remove(container);
+ }
+ }
+
+ private ImsFeatureContainer getImsFeatureContainer(int slotId, int featureType) {
+ return mImsFeatureBinders.stream()
+ .filter(f-> (f.slotId == slotId && f.featureType == featureType))
+ .findFirst().orElse(null);
+ }
+
private void notifyAllFeaturesRemoved() {
if (mCallbacks == null) {
Log.w(LOG_TAG, "notifyAllFeaturesRemoved called with invalid callbacks.");
diff --git a/com/android/internal/telephony/imsphone/ImsPhone.java b/com/android/internal/telephony/imsphone/ImsPhone.java
index bb2c34f8..e54d7bcc 100644
--- a/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -95,6 +95,7 @@ import com.android.internal.telephony.TelephonyComponentFactory;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.gsm.GsmMmiCode;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.uicc.IccRecords;
import com.android.internal.telephony.util.NotificationChannelController;
@@ -876,9 +877,8 @@ public class ImsPhone extends ImsPhoneBase {
if ((isValidCommandInterfaceCFAction(commandInterfaceCFAction)) &&
(isValidCommandInterfaceCFReason(commandInterfaceCFReason))) {
Message resp;
- Cf cf = new Cf(dialingNumber,
- (commandInterfaceCFReason == CF_REASON_UNCONDITIONAL ? true : false),
- onComplete);
+ Cf cf = new Cf(dialingNumber, GsmMmiCode.isVoiceUnconditionalForwarding(
+ commandInterfaceCFReason, serviceClass), onComplete);
resp = obtainMessage(EVENT_SET_CALL_FORWARD_DONE,
isCfEnable(commandInterfaceCFAction) ? 1 : 0, 0, cf);
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
index cea8b217..c2711e8d 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -22,13 +22,13 @@ import android.os.Message;
import android.service.carrier.CarrierIdentifier;
import android.telephony.ImsiEncryptionInfo;
import android.telephony.NetworkScanRequest;
+import android.telephony.data.DataProfile;
import com.android.internal.telephony.BaseCommands;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.RadioCapability;
import com.android.internal.telephony.UUSInfo;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
-import com.android.internal.telephony.dataconnection.DataProfile;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import java.util.List;
diff --git a/com/android/internal/telephony/sip/SipCommandInterface.java b/com/android/internal/telephony/sip/SipCommandInterface.java
index 1264053c..59af1bbd 100644
--- a/com/android/internal/telephony/sip/SipCommandInterface.java
+++ b/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -22,12 +22,12 @@ import android.os.Message;
import android.service.carrier.CarrierIdentifier;
import android.telephony.ImsiEncryptionInfo;
import android.telephony.NetworkScanRequest;
+import android.telephony.data.DataProfile;
import com.android.internal.telephony.BaseCommands;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.UUSInfo;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
-import com.android.internal.telephony.dataconnection.DataProfile;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import java.util.List;
diff --git a/com/android/internal/telephony/test/SimulatedCommands.java b/com/android/internal/telephony/test/SimulatedCommands.java
index 7553b618..b90a2924 100644
--- a/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/com/android/internal/telephony/test/SimulatedCommands.java
@@ -35,6 +35,7 @@ import android.telephony.NetworkScanRequest;
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
+import android.telephony.data.DataProfile;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.BaseCommands;
@@ -49,7 +50,6 @@ import com.android.internal.telephony.SmsResponse;
import com.android.internal.telephony.UUSInfo;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
import com.android.internal.telephony.dataconnection.DataCallResponse;
-import com.android.internal.telephony.dataconnection.DataProfile;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.uicc.IccCardStatus;
@@ -132,6 +132,7 @@ public class SimulatedCommands extends BaseCommands
private boolean mDcSuccess = true;
private DataCallResponse mDcResponse;
+ private boolean mIsRadioPowerFailResponse = false;
//***** Constructor
public
@@ -1188,11 +1189,17 @@ public class SimulatedCommands extends BaseCommands
@Override
public void setRadioPower(boolean on, Message result) {
+ if (mIsRadioPowerFailResponse) {
+ resultFail(result, null, new RuntimeException("setRadioPower failed!"));
+ return;
+ }
+
if(on) {
setRadioState(RadioState.RADIO_ON);
} else {
setRadioState(RadioState.RADIO_OFF);
}
+ resultSuccess(result, null);
}
@@ -2151,4 +2158,8 @@ public class SimulatedCommands extends BaseCommands
super.setOnRestrictedStateChanged(h, what, obj);
SimulatedCommandsVerifier.getInstance().setOnRestrictedStateChanged(h, what, obj);
}
+
+ public void setRadioPowerFailResponse(boolean fail) {
+ mIsRadioPowerFailResponse = fail;
+ }
}
diff --git a/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
index b03e70b6..c51b6adb 100644
--- a/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
+++ b/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
@@ -21,12 +21,12 @@ import android.os.Message;
import android.service.carrier.CarrierIdentifier;
import android.telephony.ImsiEncryptionInfo;
import android.telephony.NetworkScanRequest;
+import android.telephony.data.DataProfile;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.RadioCapability;
import com.android.internal.telephony.UUSInfo;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
-import com.android.internal.telephony.dataconnection.DataProfile;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import java.util.List;
diff --git a/com/android/internal/telephony/uicc/SIMRecords.java b/com/android/internal/telephony/uicc/SIMRecords.java
index 8594f804..3af04a9e 100644
--- a/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/com/android/internal/telephony/uicc/SIMRecords.java
@@ -60,9 +60,6 @@ public class SIMRecords extends IccRecords {
VoiceMailConstants mVmConfig;
-
- SpnOverride mSpnOverride;
-
// ***** Cached SIM State; cleared on channel close
private int mCallForwardingStatus;
@@ -99,7 +96,6 @@ public class SIMRecords extends IccRecords {
public String toString() {
return "SimRecords: " + super.toString()
+ " mVmConfig" + mVmConfig
- + " mSpnOverride=" + mSpnOverride
+ " callForwardingEnabled=" + mCallForwardingStatus
+ " spnState=" + mSpnState
+ " mCphsInfo=" + mCphsInfo
@@ -214,7 +210,6 @@ public class SIMRecords extends IccRecords {
mAdnCache = new AdnRecordCache(mFh);
mVmConfig = new VoiceMailConstants();
- mSpnOverride = new SpnOverride();
mRecordsRequested = false; // No load request is made till SIM ready
@@ -1640,57 +1635,63 @@ public class SIMRecords extends IccRecords {
//***** Private methods
+ /**
+ * Override the carrier name with either carrier config or SPN
+ * if an override is provided.
+ */
private void handleCarrierNameOverride() {
+ final int phoneId = mParentApp.getPhoneId();
+ SubscriptionController subCon = SubscriptionController.getInstance();
+ final int subId = subCon.getSubIdUsingPhoneId(phoneId);
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ loge("subId not valid for Phone " + phoneId);
+ return;
+ }
+
CarrierConfigManager configLoader = (CarrierConfigManager)
mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- if (configLoader != null && configLoader.getConfig().getBoolean(
- CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL)) {
- String carrierName = configLoader.getConfig().getString(
- CarrierConfigManager.KEY_CARRIER_NAME_STRING);
- setServiceProviderName(carrierName);
- mTelephonyManager.setSimOperatorNameForPhone(mParentApp.getPhoneId(),
- carrierName);
- } else {
- setSpnFromConfig(getOperatorNumeric());
+ if (configLoader == null) {
+ loge("Failed to load a Carrier Config");
+ return;
}
- /* update display name with carrier override */
- setDisplayName();
+ PersistableBundle config = configLoader.getConfigForSubId(subId);
+ boolean preferCcName = config.getBoolean(
+ CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false);
+ String ccName = config.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
+ // If carrier config is priority, use it regardless - the preference
+ // and the name were both set by the carrier, so this is safe;
+ // otherwise, if the SPN is priority but we don't have one *and* we have
+ // a name in carrier config, use the carrier config name as a backup.
+ if (preferCcName || (TextUtils.isEmpty(getServiceProviderName())
+ && !TextUtils.isEmpty(ccName))) {
+ setServiceProviderName(ccName);
+ mTelephonyManager.setSimOperatorNameForPhone(phoneId, ccName);
+ }
+
+ updateCarrierNameForSubscription(subCon, subId);
}
- private void setDisplayName() {
- SubscriptionManager subManager = SubscriptionManager.from(mContext);
- int[] subId = subManager.getSubId(mParentApp.getPhoneId());
+ private void updateCarrierNameForSubscription(SubscriptionController subCon, int subId) {
+ /* update display name with carrier override */
+ SubscriptionInfo subInfo = subCon.getActiveSubscriptionInfo(
+ subId, mContext.getOpPackageName());
- if ((subId == null) || subId.length <= 0) {
- log("subId not valid for Phone " + mParentApp.getPhoneId());
+ if (subInfo == null || subInfo.getNameSource()
+ == SubscriptionManager.NAME_SOURCE_USER_INPUT) {
+ // either way, there is no subinfo to update
return;
}
- SubscriptionInfo subInfo = subManager.getActiveSubscriptionInfo(subId[0]);
- if (subInfo != null && subInfo.getNameSource()
- != SubscriptionManager.NAME_SOURCE_USER_INPUT) {
- CharSequence oldSubName = subInfo.getDisplayName();
- String newCarrierName = mTelephonyManager.getSimOperatorName(subId[0]);
+ CharSequence oldSubName = subInfo.getDisplayName();
+ String newCarrierName = mTelephonyManager.getSimOperatorName(subId);
- if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) {
- log("sim name[" + mParentApp.getPhoneId() + "] = " + newCarrierName);
- SubscriptionController.getInstance().setDisplayName(newCarrierName, subId[0]);
- }
- } else {
- log("SUB[" + mParentApp.getPhoneId() + "] " + subId[0] + " SubInfo not created yet");
+ if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) {
+ log("sim name[" + mParentApp.getPhoneId() + "] = " + newCarrierName);
+ subCon.setDisplayName(newCarrierName, subId);
}
}
- private void setSpnFromConfig(String carrier) {
- if (mSpnOverride.containsCarrier(carrier)) {
- setServiceProviderName(mSpnOverride.getSpn(carrier));
- mTelephonyManager.setSimOperatorNameForPhone(
- mParentApp.getPhoneId(), getServiceProviderName());
- }
- }
-
-
private void setVoiceMailByCountry (String spn) {
if (mVmConfig.containsCarrier(spn)) {
mIsVoiceMailFixed = true;
@@ -2223,7 +2224,6 @@ public class SIMRecords extends IccRecords {
pw.println(" extends:");
super.dump(fd, pw, args);
pw.println(" mVmConfig=" + mVmConfig);
- pw.println(" mSpnOverride=" + mSpnOverride);
pw.println(" mCallForwardingStatus=" + mCallForwardingStatus);
pw.println(" mSpnState=" + mSpnState);
pw.println(" mCphsInfo=" + mCphsInfo);
diff --git a/com/android/internal/telephony/uicc/SpnOverride.java b/com/android/internal/telephony/uicc/SpnOverride.java
deleted file mode 100644
index 3a01af62..00000000
--- a/com/android/internal/telephony/uicc/SpnOverride.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony.uicc;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.HashMap;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.os.Environment;
-import android.telephony.Rlog;
-import android.util.Xml;
-
-import com.android.internal.util.XmlUtils;
-
-public class SpnOverride {
- private HashMap<String, String> mCarrierSpnMap;
-
-
- static final String LOG_TAG = "SpnOverride";
- static final String PARTNER_SPN_OVERRIDE_PATH ="etc/spn-conf.xml";
- static final String OEM_SPN_OVERRIDE_PATH = "telephony/spn-conf.xml";
-
- SpnOverride () {
- mCarrierSpnMap = new HashMap<String, String>();
- loadSpnOverrides();
- }
-
- boolean containsCarrier(String carrier) {
- return mCarrierSpnMap.containsKey(carrier);
- }
-
- String getSpn(String carrier) {
- return mCarrierSpnMap.get(carrier);
- }
-
- private void loadSpnOverrides() {
- FileReader spnReader;
-
- File spnFile = new File(Environment.getRootDirectory(),
- PARTNER_SPN_OVERRIDE_PATH);
- File oemSpnFile = new File(Environment.getOemDirectory(),
- OEM_SPN_OVERRIDE_PATH);
-
- if (oemSpnFile.exists()) {
- // OEM image exist SPN xml, get the timestamp from OEM & System image for comparison.
- long oemSpnTime = oemSpnFile.lastModified();
- long sysSpnTime = spnFile.lastModified();
- Rlog.d(LOG_TAG, "SPN Timestamp: oemTime = " + oemSpnTime + " sysTime = " + sysSpnTime);
-
- // To get the newer version of SPN from OEM image
- if (oemSpnTime > sysSpnTime) {
- Rlog.d(LOG_TAG, "SPN in OEM image is newer than System image");
- spnFile = oemSpnFile;
- }
- } else {
- // No SPN in OEM image, so load it from system image.
- Rlog.d(LOG_TAG, "No SPN in OEM image = " + oemSpnFile.getPath() +
- " Load SPN from system image");
- }
-
- try {
- spnReader = new FileReader(spnFile);
- } catch (FileNotFoundException e) {
- Rlog.w(LOG_TAG, "Can not open " + spnFile.getAbsolutePath());
- return;
- }
-
- try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(spnReader);
-
- XmlUtils.beginDocument(parser, "spnOverrides");
-
- while (true) {
- XmlUtils.nextElement(parser);
-
- String name = parser.getName();
- if (!"spnOverride".equals(name)) {
- break;
- }
-
- String numeric = parser.getAttributeValue(null, "numeric");
- String data = parser.getAttributeValue(null, "spn");
-
- mCarrierSpnMap.put(numeric, data);
- }
- spnReader.close();
- } catch (XmlPullParserException e) {
- Rlog.w(LOG_TAG, "Exception in spn-conf parser " + e);
- } catch (IOException e) {
- Rlog.w(LOG_TAG, "Exception in spn-conf parser " + e);
- }
- }
-
-}
diff --git a/com/android/internal/util/ArrayUtils.java b/com/android/internal/util/ArrayUtils.java
index 91bc6813..22bfcc34 100644
--- a/com/android/internal/util/ArrayUtils.java
+++ b/com/android/internal/util/ArrayUtils.java
@@ -30,6 +30,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -134,6 +135,13 @@ public class ArrayUtils {
}
/**
+ * Checks if given map is null or has zero elements.
+ */
+ public static boolean isEmpty(@Nullable Map<?, ?> map) {
+ return map == null || map.isEmpty();
+ }
+
+ /**
* Checks if given array is null or has zero elements.
*/
public static <T> boolean isEmpty(@Nullable T[] array) {
diff --git a/com/android/internal/util/RingBuffer.java b/com/android/internal/util/RingBuffer.java
index c22be2cc..9a6e542c 100644
--- a/com/android/internal/util/RingBuffer.java
+++ b/com/android/internal/util/RingBuffer.java
@@ -60,6 +60,25 @@ public class RingBuffer<T> {
mBuffer[indexOf(mCursor++)] = t;
}
+ /**
+ * Returns object of type <T> at the next writable slot, creating one if it is not already
+ * available. In case of any errors while creating the object, <code>null</code> will
+ * be returned.
+ */
+ public T getNextSlot() {
+ final int nextSlotIdx = indexOf(mCursor++);
+ T item = mBuffer[nextSlotIdx];
+ if (item == null) {
+ try {
+ item = (T) mBuffer.getClass().getComponentType().newInstance();
+ } catch (IllegalAccessException | InstantiationException e) {
+ return null;
+ }
+ mBuffer[nextSlotIdx] = item;
+ }
+ return item;
+ }
+
public T[] toArray() {
// Only generic way to create a T[] from another T[]
T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
diff --git a/com/android/internal/util/UserIcons.java b/com/android/internal/util/UserIcons.java
index daf745ff..bfe43237 100644
--- a/com/android/internal/util/UserIcons.java
+++ b/com/android/internal/util/UserIcons.java
@@ -61,17 +61,19 @@ public class UserIcons {
* Returns a default user icon for the given user.
*
* Note that for guest users, you should pass in {@code UserHandle.USER_NULL}.
+ *
+ * @param resources resources object to fetch user icon / color.
* @param userId the user id or {@code UserHandle.USER_NULL} for a non-user specific icon
* @param light whether we want a light icon (suitable for a dark background)
*/
- public static Drawable getDefaultUserIcon(int userId, boolean light) {
+ public static Drawable getDefaultUserIcon(Resources resources, int userId, boolean light) {
int colorResId = light ? R.color.user_icon_default_white : R.color.user_icon_default_gray;
if (userId != UserHandle.USER_NULL) {
// Return colored icon instead
colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length];
}
- Drawable icon = Resources.getSystem().getDrawable(R.drawable.ic_account_circle, null).mutate();
- icon.setColorFilter(Resources.getSystem().getColor(colorResId, null), Mode.SRC_IN);
+ Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate();
+ icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN);
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
return icon;
}
diff --git a/com/android/internal/view/TooltipPopup.java b/com/android/internal/view/TooltipPopup.java
index d38ea2c1..24f0b0cc 100644
--- a/com/android/internal/view/TooltipPopup.java
+++ b/com/android/internal/view/TooltipPopup.java
@@ -142,7 +142,7 @@ public class TooltipPopup {
mTmpAnchorPos[1] -= mTmpAppPos[1];
// mTmpAnchorPos is now relative to the main app window.
- outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2;
+ outParams.x = mTmpAnchorPos[0] + offsetX - appView.getWidth() / 2;
final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mContentView.measure(spec, spec);
@@ -157,6 +157,9 @@ public class TooltipPopup {
outParams.y = yBelow;
}
} else {
+ // Use mTmpDisplayFrame.height() as the lower boundary instead of appView.getHeight(),
+ // as the latter includes the navigation bar, and tooltips do not look good over
+ // the navigation bar.
if (yBelow + tooltipHeight <= mTmpDisplayFrame.height()) {
outParams.y = yBelow;
} else {
diff --git a/com/android/internal/widget/ExploreByTouchHelper.java b/com/android/internal/widget/ExploreByTouchHelper.java
index 50ad547e..759a41a2 100644
--- a/com/android/internal/widget/ExploreByTouchHelper.java
+++ b/com/android/internal/widget/ExploreByTouchHelper.java
@@ -186,6 +186,9 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
}
final AccessibilityEvent event = createEvent(virtualViewId, eventType);
+ if (event == null) {
+ return false;
+ }
return parent.requestSendAccessibilityEvent(mView, event);
}
@@ -240,6 +243,9 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
if (parent != null) {
final AccessibilityEvent event = createEvent(virtualViewId,
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ if (event == null) {
+ return;
+ }
event.setContentChangeTypes(changeTypes);
parent.requestSendAccessibilityEvent(mView, event);
}
@@ -305,6 +311,9 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
* the specified item.
*/
private AccessibilityEvent createEventForHost(int eventType) {
+ if (!AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
+ return null;
+ }
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
mView.onInitializeAccessibilityEvent(event);
@@ -325,6 +334,9 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
* the specified item.
*/
private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
+ if (!AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
+ return null;
+ }
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
event.setEnabled(true);
event.setClassName(DEFAULT_CLASS_NAME);
diff --git a/com/android/internal/widget/Magnifier.java b/com/android/internal/widget/Magnifier.java
deleted file mode 100644
index f6741c3b..00000000
--- a/com/android/internal/widget/Magnifier.java
+++ /dev/null
@@ -1,218 +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 com.android.internal.widget;
-
-import android.annotation.FloatRange;
-import android.annotation.NonNull;
-import android.annotation.UiThread;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.PixelCopy;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.widget.ImageView;
-import android.widget.PopupWindow;
-
-import com.android.internal.R;
-import com.android.internal.util.Preconditions;
-
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * Android magnifier widget. Can be used by any view which is attached to window.
- */
-public final class Magnifier {
- private static final String LOG_TAG = "magnifier";
- private static final int MAGNIFIER_REFRESH_RATE_MS = 33; // ~30fps
- // The view for which this magnifier is attached.
- private final View mView;
- // The window containing the magnifier.
- private final PopupWindow mWindow;
- // The center coordinates of the window containing the magnifier.
- private final Point mWindowCoords = new Point();
- // The width of the window containing the magnifier.
- private final int mWindowWidth;
- // The height of the window containing the magnifier.
- private final int mWindowHeight;
- // The bitmap used to display the contents of the magnifier.
- private final Bitmap mBitmap;
- // The center coordinates of the content that is to be magnified.
- private final Point mCenterZoomCoords = new Point();
- // The callback of the pixel copy request will be invoked on this Handler when
- // the copy is finished.
- private final Handler mPixelCopyHandler = Handler.getMain();
- // Current magnification scale.
- private final float mZoomScale;
- // Timer used to schedule the copy task.
- private Timer mTimer;
-
- /**
- * Initializes a magnifier.
- *
- * @param view the view for which this magnifier is attached
- */
- @UiThread
- public Magnifier(@NonNull View view) {
- mView = Preconditions.checkNotNull(view);
- final Context context = mView.getContext();
- final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
- final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
- content.findViewById(R.id.magnifier_inner).setClipToOutline(true);
- mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
- mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
- mZoomScale = context.getResources().getFloat(R.dimen.magnifier_zoom_scale);
-
- mWindow = new PopupWindow(context);
- mWindow.setContentView(content);
- mWindow.setWidth(mWindowWidth);
- mWindow.setHeight(mWindowHeight);
- mWindow.setElevation(elevation);
- mWindow.setTouchable(false);
- mWindow.setBackgroundDrawable(null);
-
- final int bitmapWidth = (int) (mWindowWidth / mZoomScale);
- final int bitmapHeight = (int) (mWindowHeight / mZoomScale);
- mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
- getImageView().setImageBitmap(mBitmap);
- }
-
- /**
- * Shows the magnifier on the screen.
- *
- * @param xPosInView horizontal coordinate of the center point of the magnifier source relative
- * to the view. The lower end is clamped to 0
- * @param yPosInView vertical coordinate of the center point of the magnifier source
- * relative to the view. The lower end is clamped to 0
- */
- public void show(@FloatRange(from=0) float xPosInView, @FloatRange(from=0) float yPosInView) {
- if (xPosInView < 0) {
- xPosInView = 0;
- }
-
- if (yPosInView < 0) {
- yPosInView = 0;
- }
-
- configureCoordinates(xPosInView, yPosInView);
-
- if (mTimer == null) {
- mTimer = new Timer();
- mTimer.schedule(new TimerTask() {
- @Override
- public void run() {
- performPixelCopy();
- }
- }, 0 /* delay */, MAGNIFIER_REFRESH_RATE_MS);
- }
-
- if (mWindow.isShowing()) {
- mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
- mWindow.getHeight());
- } else {
- mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY,
- mWindowCoords.x, mWindowCoords.y);
- }
- }
-
- /**
- * Dismisses the magnifier from the screen.
- */
- public void dismiss() {
- mWindow.dismiss();
-
- if (mTimer != null) {
- mTimer.cancel();
- mTimer.purge();
- mTimer = null;
- }
- }
-
- /**
- * @return the height of the magnifier window.
- */
- public int getHeight() {
- return mWindowHeight;
- }
-
- /**
- * @return the width of the magnifier window.
- */
- public int getWidth() {
- return mWindowWidth;
- }
-
- /**
- * @return the zoom scale of the magnifier.
- */
- public float getZoomScale() {
- return mZoomScale;
- }
-
- private void configureCoordinates(float xPosInView, float yPosInView) {
- final int[] coordinatesOnScreen = new int[2];
- mView.getLocationOnScreen(coordinatesOnScreen);
- final float posXOnScreen = xPosInView + coordinatesOnScreen[0];
- final float posYOnScreen = yPosInView + coordinatesOnScreen[1];
-
- mCenterZoomCoords.x = (int) posXOnScreen;
- mCenterZoomCoords.y = (int) posYOnScreen;
-
- final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize(
- R.dimen.magnifier_offset);
- mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2;
- mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset;
- }
-
- private void performPixelCopy() {
- 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 (rawStartX < 0) {
- rawStartX = 0;
- } else if (rawStartX + mBitmap.getWidth() > mView.getWidth()) {
- rawStartX = mView.getWidth() - mBitmap.getWidth();
- }
-
- final int startX = rawStartX;
- final ViewRootImpl viewRootImpl = mView.getViewRootImpl();
-
- if (viewRootImpl != null && viewRootImpl.mSurface != null
- && viewRootImpl.mSurface.isValid()) {
- PixelCopy.request(
- viewRootImpl.mSurface,
- new Rect(startX, startY, startX + mBitmap.getWidth(),
- startY + mBitmap.getHeight()),
- mBitmap,
- result -> getImageView().invalidate(),
- mPixelCopyHandler);
- } else {
- Log.d(LOG_TAG, "Could not perform PixelCopy request");
- }
- }
-
- private ImageView getImageView() {
- return mWindow.getContentView().findViewById(R.id.magnifier_image);
- }
-}
diff --git a/com/android/internal/widget/PointerLocationView.java b/com/android/internal/widget/PointerLocationView.java
index e53162cc..5847033f 100644
--- a/com/android/internal/widget/PointerLocationView.java
+++ b/com/android/internal/widget/PointerLocationView.java
@@ -32,7 +32,7 @@ import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.view.MotionEvent.PointerCoords;
import java.util.ArrayList;
diff --git a/com/android/internal/widget/RecyclerView.java b/com/android/internal/widget/RecyclerView.java
index 7abc76a8..408a4e9b 100644
--- a/com/android/internal/widget/RecyclerView.java
+++ b/com/android/internal/widget/RecyclerView.java
@@ -9556,7 +9556,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
if (vScroll == 0 && hScroll == 0) {
return false;
}
- mRecyclerView.scrollBy(hScroll, vScroll);
+ mRecyclerView.smoothScrollBy(hScroll, vScroll);
return true;
}
diff --git a/com/android/keyguard/KeyguardSliceView.java b/com/android/keyguard/KeyguardSliceView.java
index c18f9b61..cb3d59c4 100644
--- a/com/android/keyguard/KeyguardSliceView.java
+++ b/com/android/keyguard/KeyguardSliceView.java
@@ -33,6 +33,8 @@ import com.android.internal.graphics.ColorUtils;
import com.android.systemui.R;
import com.android.systemui.keyguard.KeyguardSliceProvider;
+import java.util.Collections;
+
/**
* View visible under the clock on the lock screen and AoD.
*/
@@ -75,7 +77,8 @@ public class KeyguardSliceView extends LinearLayout {
super.onAttachedToWindow();
// Set initial content
- showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+ showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
+ Collections.emptyList()));
// Make sure we always have the most current slice
getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
@@ -91,7 +94,7 @@ public class KeyguardSliceView extends LinearLayout {
private void showSlice(Slice slice) {
// Items will be wrapped into an action when they have tap targets.
- SliceItem actionSlice = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+ SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION);
if (actionSlice != null) {
mSlice = actionSlice.getSlice();
mSliceAction = actionSlice.getAction();
@@ -105,7 +108,7 @@ public class KeyguardSliceView extends LinearLayout {
return;
}
- SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null);
+ SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null);
if (title == null) {
mTitle.setVisibility(GONE);
} else {
@@ -113,7 +116,7 @@ public class KeyguardSliceView extends LinearLayout {
mTitle.setText(title.getText());
}
- SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE);
+ SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE);
if (text == null) {
mText.setVisibility(GONE);
} else {
@@ -154,7 +157,8 @@ public class KeyguardSliceView extends LinearLayout {
@Override
public void onChange(boolean selfChange, Uri uri) {
- showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+ showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
+ Collections.emptyList()));
}
}
}
diff --git a/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 5a02178a..8d55eea4 100644
--- a/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -21,7 +21,7 @@ import android.media.AudioManager;
import android.os.SystemClock;
import android.hardware.fingerprint.FingerprintManager;
import android.telephony.TelephonyManager;
-import android.view.WindowManagerPolicy;
+import android.view.WindowManagerPolicyConstants;
import com.android.internal.telephony.IccCardConstants;
@@ -171,9 +171,9 @@ public class KeyguardUpdateMonitorCallback {
/**
* Called when the device has finished going to sleep.
- * @param why either {@link WindowManagerPolicy#OFF_BECAUSE_OF_ADMIN},
- * {@link WindowManagerPolicy#OFF_BECAUSE_OF_USER}, or
- * {@link WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
+ * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_ADMIN},
+ * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER}, or
+ * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}.
*
* @deprecated use {@link com.android.systemui.keyguard.WakefulnessLifecycle}.
*/
diff --git a/com/android/keyguard/PasswordTextView.java b/com/android/keyguard/PasswordTextView.java
index 12f75bb2..0219db33 100644
--- a/com/android/keyguard/PasswordTextView.java
+++ b/com/android/keyguard/PasswordTextView.java
@@ -307,8 +307,9 @@ public class PasswordTextView extends View {
void sendAccessibilityEventTypeViewTextChanged(String beforeText, int fromIndex,
int removedCount, int addedCount) {
- if (AccessibilityManager.getInstance(mContext).isEnabled() &&
- (isFocused() || isSelected() && isShown())) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)
+ && (isFocused() || isSelected() && isShown())) {
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
event.setFromIndex(fromIndex);
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/layoutlib/bridge/android/BridgePackageManager.java b/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 98937ef3..37dc166f 100644
--- a/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -26,12 +26,11 @@ import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ChangedPackages;
-import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstantAppInfo;
import android.content.pm.InstrumentationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
@@ -56,6 +55,7 @@ import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.os.storage.VolumeInfo;
+
import java.util.List;
/**
@@ -611,11 +611,6 @@ public class BridgePackageManager extends PackageManager {
}
@Override
- public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
- String installerPackageName) {
- }
-
- @Override
public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags,
String installerPackageName) {
}
diff --git a/com/android/providers/settings/SettingsProtoDumpUtil.java b/com/android/providers/settings/SettingsProtoDumpUtil.java
index 41b205bc..52b4f4df 100644
--- a/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -801,6 +801,9 @@ class SettingsProtoDumpUtil {
Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX,
GlobalSettingsProto.BLUETOOTH_PAN_PRIORITY_PREFIX);
dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX);
+ dumpSetting(s, p,
Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
GlobalSettingsProto.ACTIVITY_MANAGER_CONSTANTS);
dumpSetting(s, p,
@@ -869,6 +872,15 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Global.WAIT_FOR_DEBUGGER,
GlobalSettingsProto.WAIT_FOR_DEBUGGER);
+ dumpSetting(s, p,
+ Settings.Global.ENABLE_GPU_DEBUG_LAYERS,
+ GlobalSettingsProto.ENABLE_GPU_DEBUG_LAYERS);
+ dumpSetting(s, p,
+ Settings.Global.GPU_DEBUG_APP,
+ GlobalSettingsProto.GPU_DEBUG_APP);
+ dumpSetting(s, p,
+ Settings.Global.GPU_DEBUG_LAYERS,
+ GlobalSettingsProto.GPU_DEBUG_LAYERS);
// Settings.Global.SHOW_PROCESSES intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Global.LOW_POWER_MODE,
diff --git a/com/android/providers/settings/SettingsProvider.java b/com/android/providers/settings/SettingsProvider.java
index 258c96cf..7fb6ede8 100644
--- a/com/android/providers/settings/SettingsProvider.java
+++ b/com/android/providers/settings/SettingsProvider.java
@@ -61,6 +61,7 @@ import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -343,7 +344,8 @@ public class SettingsProvider extends ContentProvider {
}
case Settings.CALL_METHOD_GET_SECURE: {
- Setting setting = getSecureSetting(name, requestingUserId);
+ Setting setting = getSecureSetting(name, requestingUserId,
+ /*enableOverride=*/ true);
return packageValueForCallResult(setting, isTrackingGeneration(args));
}
@@ -1073,6 +1075,10 @@ public class SettingsProvider extends ContentProvider {
}
private Setting getSecureSetting(String name, int requestingUserId) {
+ return getSecureSetting(name, requestingUserId, /*enableOverride=*/ false);
+ }
+
+ private Setting getSecureSetting(String name, int requestingUserId, boolean enableOverride) {
if (DEBUG) {
Slog.v(LOG_TAG, "getSecureSetting(" + name + ", " + requestingUserId + ")");
}
@@ -1102,6 +1108,14 @@ public class SettingsProvider extends ContentProvider {
return getSsaidSettingLocked(callingPkg, owningUserId);
}
}
+ if (enableOverride) {
+ if (Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
+ final Setting overridden = getLocationProvidersAllowedSetting(owningUserId);
+ if (overridden != null) {
+ return overridden;
+ }
+ }
+ }
// Not the SSAID; do a straight lookup
synchronized (mLock) {
@@ -1190,6 +1204,35 @@ public class SettingsProvider extends ContentProvider {
return null;
}
+ private Setting getLocationProvidersAllowedSetting(int owningUserId) {
+ synchronized (mLock) {
+ final Setting setting = getGlobalSetting(
+ Global.LOCATION_GLOBAL_KILL_SWITCH);
+ if (!"1".equals(setting.getValue())) {
+ return null;
+ }
+ // Global kill-switch is enabled. Return an empty value.
+ final SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
+ SETTINGS_TYPE_SECURE, owningUserId);
+ return settingsState.new Setting(
+ Secure.LOCATION_PROVIDERS_ALLOWED,
+ "", // value
+ "", // tag
+ "", // default value
+ "", // package name
+ false, // from system
+ "0" // id
+ ) {
+ @Override
+ public boolean update(String value, boolean setDefault, String packageName,
+ String tag, boolean forceNonSystemPackage) {
+ Slog.wtf(LOG_TAG, "update shoudln't be called on this instance.");
+ return false;
+ }
+ };
+ }
+ }
+
private boolean insertSecureSetting(String name, String value, String tag,
boolean makeDefault, int requestingUserId, boolean forceNotify) {
if (DEBUG) {
@@ -2780,6 +2823,12 @@ public class SettingsProvider extends ContentProvider {
}
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
+
+ // When the global kill switch is updated, send the change notification for
+ // the location setting.
+ if (isGlobalSettingsKey(key) && Global.LOCATION_GLOBAL_KILL_SWITCH.equals(name)) {
+ notifyLocationChangeForRunningUsers();
+ }
}
private void maybeNotifyProfiles(int type, int userId, Uri uri, String name,
@@ -2799,6 +2848,24 @@ public class SettingsProvider extends ContentProvider {
}
}
+ private void notifyLocationChangeForRunningUsers() {
+ final List<UserInfo> users = mUserManager.getUsers(/*excludeDying=*/ true);
+
+ for (int i = 0; i < users.size(); i++) {
+ final int userId = users.get(i).id;
+
+ if (!mUserManager.isUserRunning(UserHandle.of(userId))) {
+ continue;
+ }
+
+ final int key = makeKey(SETTINGS_TYPE_GLOBAL, userId);
+ final Uri uri = getNotificationUriFor(key, Secure.LOCATION_PROVIDERS_ALLOWED);
+
+ mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
+ userId, 0, uri).sendToTarget();
+ }
+ }
+
private boolean isGlobalSettingsKey(int key) {
return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
}
@@ -2885,7 +2952,7 @@ public class SettingsProvider extends ContentProvider {
} catch (SecurityException e) {
Slog.w(LOG_TAG, "Failed to notify for " + userId + ": " + uri, e);
}
- if (DEBUG) {
+ if (DEBUG || true) {
Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri);
}
} break;
diff --git a/com/android/server/AlarmManagerService.java b/com/android/server/AlarmManagerService.java
index 3904fc96..ca152495 100644
--- a/com/android/server/AlarmManagerService.java
+++ b/com/android/server/AlarmManagerService.java
@@ -46,16 +46,15 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -81,6 +80,7 @@ import java.util.Locale;
import java.util.Random;
import java.util.TimeZone;
import java.util.TreeSet;
+import java.util.function.Predicate;
import static android.app.AlarmManager.RTC_WAKEUP;
import static android.app.AlarmManager.RTC;
@@ -88,11 +88,17 @@ import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.app.AlarmManager.ELAPSED_REALTIME;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsService;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.LocalLog;
+import com.android.server.ForceAppStandbyTracker.Listener;
+/**
+ * Alarm manager implementaion.
+ *
+ * Unit test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
+ */
class AlarmManagerService extends SystemService {
private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP;
private static final int RTC_MASK = 1 << RTC;
@@ -131,13 +137,10 @@ class AlarmManagerService extends SystemService {
final LocalLog mLog = new LocalLog(TAG);
AppOpsManager mAppOps;
- IAppOpsService mAppOpsService;
DeviceIdleController.LocalService mLocalDeviceIdleController;
final Object mLock = new Object();
- ArraySet<String> mForcedAppStandbyPackages = new ArraySet<>();
- SparseBooleanArray mForegroundUids = new SparseBooleanArray();
// List of alarms per uid deferred due to user applied background restrictions on the source app
SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
long mNativeData;
@@ -184,12 +187,6 @@ class AlarmManagerService extends SystemService {
int mSystemUiUid;
/**
- * The current set of user whitelisted apps for device idle mode, meaning these are allowed
- * to freely schedule alarms.
- */
- int[] mDeviceIdleUserWhitelist = new int[0];
-
- /**
* For each uid, this is the last time we dispatched an "allow while idle" alarm,
* used to determine the earliest we can dispatch the next such alarm. Times are in the
* 'elapsed' timebase.
@@ -223,6 +220,8 @@ class AlarmManagerService extends SystemService {
private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
new SparseArray<>();
+ private final ForceAppStandbyTracker mForceAppStandbyTracker;
+
/**
* All times are in milliseconds. These constants are kept synchronized with the system
* global Settings. Any access to this class or its fields should be done while
@@ -757,6 +756,9 @@ class AlarmManagerService extends SystemService {
public AlarmManagerService(Context context) {
super(context);
mConstants = new Constants(mHandler);
+
+ mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+ mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
}
static long convertToElapsed(long when, int type) {
@@ -894,17 +896,48 @@ class AlarmManagerService extends SystemService {
deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
}
- void sendPendingBackgroundAlarmsForAppIdLocked(int appId) {
+ /**
+ * Check all alarms in {@link #mPendingBackgroundAlarms} and send the ones that are not
+ * restricted.
+ *
+ * This is only called when the global "force all apps-standby" flag changes or when the
+ * power save whitelist changes, so it's okay to be slow.
+ */
+ void sendAllUnrestrictedPendingBackgroundAlarmsLocked() {
final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
- for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
- final int uid = mPendingBackgroundAlarms.keyAt(i);
- final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
- if (UserHandle.getAppId(uid) == appId) {
- alarmsToDeliver.addAll(alarmsForUid);
- mPendingBackgroundAlarms.removeAt(i);
+
+ findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+ mPendingBackgroundAlarms, alarmsToDeliver, this::isBackgroundRestricted);
+
+ if (alarmsToDeliver.size() > 0) {
+ deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
+ }
+ }
+
+ @VisibleForTesting
+ static void findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+ SparseArray<ArrayList<Alarm>> pendingAlarms, ArrayList<Alarm> unrestrictedAlarms,
+ Predicate<Alarm> isBackgroundRestricted) {
+
+ for (int uidIndex = pendingAlarms.size() - 1; uidIndex >= 0; uidIndex--) {
+ final int uid = pendingAlarms.keyAt(uidIndex);
+ final ArrayList<Alarm> alarmsForUid = pendingAlarms.valueAt(uidIndex);
+
+ for (int alarmIndex = alarmsForUid.size() - 1; alarmIndex >= 0; alarmIndex--) {
+ final Alarm alarm = alarmsForUid.get(alarmIndex);
+
+ if (isBackgroundRestricted.test(alarm)) {
+ continue;
+ }
+
+ unrestrictedAlarms.add(alarm);
+ alarmsForUid.remove(alarmIndex);
+ }
+
+ if (alarmsForUid.size() == 0) {
+ pendingAlarms.removeAt(uidIndex);
}
}
- deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
}
private void deliverPendingBackgroundAlarmsLocked(ArrayList<Alarm> alarms, long nowELAPSED) {
@@ -1234,10 +1267,8 @@ class AlarmManagerService extends SystemService {
} catch (RemoteException e) {
// ignored; both services live in system_server
}
- mAppOpsService = IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE));
publishBinderService(Context.ALARM_SERVICE, mService);
- publishLocalService(LocalService.class, new LocalService());
+ mForceAppStandbyTracker.start();
}
@Override
@@ -1247,13 +1278,6 @@ class AlarmManagerService extends SystemService {
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
- try {
- mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
- new AppOpsWatcher());
- } catch (RemoteException rexc) {
- // Shouldn't happen as they are in the same process.
- Slog.e(TAG, "AppOps service not reachable", rexc);
- }
}
}
@@ -1582,8 +1606,7 @@ class AlarmManagerService extends SystemService {
// timing restrictions.
} else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
|| callingUid == mSystemUiUid
- || Arrays.binarySearch(mDeviceIdleUserWhitelist,
- UserHandle.getAppId(callingUid)) >= 0)) {
+ || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) {
flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
}
@@ -1660,24 +1683,14 @@ class AlarmManagerService extends SystemService {
}
};
- public final class LocalService {
- public void setDeviceIdleUserWhitelist(int[] appids) {
- setDeviceIdleUserWhitelistImpl(appids);
- }
- }
-
void dumpImpl(PrintWriter pw) {
synchronized (mLock) {
pw.println("Current Alarm Manager state:");
mConstants.dump(pw);
pw.println();
- pw.print(" Foreground uids: [");
- for (int i = 0; i < mForegroundUids.size(); i++) {
- if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
- }
- pw.println("]");
- pw.println(" Forced app standby packages: " + mForcedAppStandbyPackages);
+ mForceAppStandbyTracker.dump(pw, " ");
+
final long nowRTC = System.currentTimeMillis();
final long nowELAPSED = SystemClock.elapsedRealtime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -1717,7 +1730,6 @@ class AlarmManagerService extends SystemService {
pw.print(" set at "); TimeUtils.formatDuration(mLastWakeupSet, nowELAPSED, pw);
pw.println();
pw.print(" Num time change events: "); pw.println(mNumTimeChanged);
- pw.println(" mDeviceIdleUserWhitelist=" + Arrays.toString(mDeviceIdleUserWhitelist));
pw.println();
pw.println(" Next alarm clock information: ");
@@ -1990,15 +2002,8 @@ class AlarmManagerService extends SystemService {
mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
- final int foregroundUidsSize = mForegroundUids.size();
- for (int i = 0; i < foregroundUidsSize; i++) {
- if (mForegroundUids.valueAt(i)) {
- proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
- }
- }
- for (String pkg : mForcedAppStandbyPackages) {
- proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg);
- }
+ mForceAppStandbyTracker.dumpProto(proto,
+ AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER);
proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
if (!mInteractive) {
@@ -2022,9 +2027,6 @@ class AlarmManagerService extends SystemService {
proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
nowElapsed - mLastWakeupSet);
proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
- for (int i : mDeviceIdleUserWhitelist) {
- proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i);
- }
final TreeSet<Integer> users = new TreeSet<>();
final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size();
@@ -2266,28 +2268,6 @@ class AlarmManagerService extends SystemService {
}
}
- void setDeviceIdleUserWhitelistImpl(int[] appids) {
- synchronized (mLock) {
- // appids are sorted, just send pending alarms for any new appids added to the whitelist
- int i = 0, j = 0;
- while (i < appids.length) {
- while (j < mDeviceIdleUserWhitelist.length
- && mDeviceIdleUserWhitelist[j] < appids[i]) {
- j++;
- }
- if (j < mDeviceIdleUserWhitelist.length
- && appids[i] != mDeviceIdleUserWhitelist[j]) {
- if (DEBUG_BG_LIMIT) {
- Slog.d(TAG, "Sending blocked alarms for whitelisted appid " + appids[j]);
- }
- sendPendingBackgroundAlarmsForAppIdLocked(appids[j]);
- }
- i++;
- }
- mDeviceIdleUserWhitelist = appids;
- }
- }
-
AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
synchronized (mLock) {
return mNextAlarmClockForUser.get(userId);
@@ -2710,9 +2690,7 @@ class AlarmManagerService extends SystemService {
final String sourcePackage =
(alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName;
final int sourceUid = alarm.creatorUid;
- return mForcedAppStandbyPackages.contains(sourcePackage) && !mForegroundUids.get(sourceUid)
- && Arrays.binarySearch(mDeviceIdleUserWhitelist, UserHandle.getAppId(sourceUid))
- < 0;
+ return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage);
}
private native long init();
@@ -2859,7 +2837,8 @@ class AlarmManagerService extends SystemService {
}
}
- private static class Alarm {
+ @VisibleForTesting
+ static class Alarm {
public final int type;
public final long origWhen;
public final boolean wakeup;
@@ -3073,6 +3052,11 @@ class AlarmManagerService extends SystemService {
for (int i=0; i<triggerList.size(); i++) {
Alarm alarm = triggerList.get(i);
final boolean allowWhileIdle = (alarm.flags&AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0;
+ if (alarm.wakeup) {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch wakeup alarm to " + alarm.packageName);
+ } else {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch non-wakeup alarm to " + alarm.packageName);
+ }
try {
if (localLOGV) {
Slog.v(TAG, "sending alarm " + alarm);
@@ -3092,6 +3076,7 @@ class AlarmManagerService extends SystemService {
} catch (RuntimeException e) {
Slog.w(TAG, "Failure sending alarm.", e);
}
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
@@ -3476,17 +3461,10 @@ class AlarmManagerService extends SystemService {
if (disabled) {
removeForStoppedLocked(uid);
}
- mForegroundUids.delete(uid);
}
}
@Override public void onUidActive(int uid) {
- synchronized (mLock) {
- if (!mForegroundUids.get(uid)) {
- mForegroundUids.put(uid, true);
- sendPendingBackgroundAlarmsLocked(uid, null);
- }
- }
}
@Override public void onUidIdle(int uid, boolean disabled) {
@@ -3494,7 +3472,6 @@ class AlarmManagerService extends SystemService {
if (disabled) {
removeForStoppedLocked(uid);
}
- mForegroundUids.delete(uid);
}
}
@@ -3502,27 +3479,29 @@ class AlarmManagerService extends SystemService {
}
};
- private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+
+ private final Listener mForceAppStandbyListener = new Listener() {
@Override
- public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ public void unblockAllUnrestrictedAlarms() {
synchronized (mLock) {
- final int mode = mAppOpsService.checkOperation(op, uid, packageName);
- if (DEBUG_BG_LIMIT) {
- Slog.d(TAG,
- "Appop changed for " + uid + ", " + packageName + " to " + mode);
- }
- final boolean changed;
- if (mode != AppOpsManager.MODE_ALLOWED) {
- changed = mForcedAppStandbyPackages.add(packageName);
- } else {
- changed = mForcedAppStandbyPackages.remove(packageName);
- }
- if (changed && mode == AppOpsManager.MODE_ALLOWED) {
- sendPendingBackgroundAlarmsLocked(uid, packageName);
- }
+ sendAllUnrestrictedPendingBackgroundAlarmsLocked();
}
}
- }
+
+ @Override
+ public void unblockAlarmsForUid(int uid) {
+ synchronized (mLock) {
+ sendPendingBackgroundAlarmsLocked(uid, null);
+ }
+ }
+
+ @Override
+ public void unblockAlarmsForUidPackage(int uid, String packageName) {
+ synchronized (mLock) {
+ sendPendingBackgroundAlarmsLocked(uid, packageName);
+ }
+ }
+ };
private final BroadcastStats getStatsLocked(PendingIntent pi) {
String pkg = pi.getCreatorPackage();
diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java
index ea0ed271..924e736b 100644
--- a/com/android/server/BatteryService.java
+++ b/com/android/server/BatteryService.java
@@ -619,6 +619,7 @@ public final class BatteryService extends SystemService {
intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.batteryHealth);
intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.batteryPresent);
intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.batteryLevel);
+ intent.putExtra(BatteryManager.EXTRA_BATTERY_LOW, mSentLowBatteryBroadcast);
intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
diff --git a/com/android/server/BluetoothManagerService.java b/com/android/server/BluetoothManagerService.java
index c34c30cf..04279a31 100644
--- a/com/android/server/BluetoothManagerService.java
+++ b/com/android/server/BluetoothManagerService.java
@@ -2150,31 +2150,26 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
(int)((onDuration / (1000 * 60)) % 60),
(int)((onDuration / 1000) % 60),
(int)(onDuration % 1000));
- writer.println(" time since enabled: " + onDurationString + "\n");
+ writer.println(" time since enabled: " + onDurationString);
}
if (mActiveLogs.size() == 0) {
- writer.println("Bluetooth never enabled!");
+ writer.println("\nBluetooth never enabled!");
} else {
- writer.println("Enable log:");
+ writer.println("\nEnable log:");
for (ActiveLog log : mActiveLogs) {
writer.println(" " + log);
}
}
- writer.println("Bluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
+ writer.println("\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
if (mCrashes == CRASH_LOG_MAX_SIZE) writer.println("(last " + CRASH_LOG_MAX_SIZE + ")");
for (Long time : mCrashTimestamps) {
writer.println(" " + timeToLog(time.longValue()));
}
- String bleAppString = "No BLE Apps registered.";
- if (mBleApps.size() == 1) {
- bleAppString = "1 BLE App registered:";
- } else if (mBleApps.size() > 1) {
- bleAppString = mBleApps.size() + " BLE Apps registered:";
- }
- writer.println("\n" + bleAppString);
+ writer.println("\n" + mBleApps.size() + " BLE app" +
+ (mBleApps.size() == 1 ? "" : "s") + "registered");
for (ClientDeathRecipient app : mBleApps.values()) {
writer.println(" " + app.getPackageName());
}
diff --git a/com/android/server/DeviceIdleController.java b/com/android/server/DeviceIdleController.java
index 0921a00b..d7aeb8ce 100644
--- a/com/android/server/DeviceIdleController.java
+++ b/com/android/server/DeviceIdleController.java
@@ -119,7 +119,6 @@ public class DeviceIdleController extends SystemService
private PowerManagerInternal mLocalPowerManager;
private PowerManager mPowerManager;
private ConnectivityService mConnectivityService;
- private AlarmManagerService.LocalService mLocalAlarmManager;
private INetworkPolicyManager mNetworkPolicyManager;
private SensorManager mSensorManager;
private Sensor mMotionSensor;
@@ -1435,7 +1434,6 @@ public class DeviceIdleController extends SystemService
mGoingIdleWakeLock.setReferenceCounted(true);
mConnectivityService = (ConnectivityService)ServiceManager.getService(
Context.CONNECTIVITY_SERVICE);
- mLocalAlarmManager = getLocalService(AlarmManagerService.LocalService.class);
mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class);
@@ -1500,8 +1498,8 @@ public class DeviceIdleController extends SystemService
mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
- mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
+ passWhiteListToForceAppStandbyTrackerLocked();
updateInteractivityLocked();
}
updateConnectivityState(null);
@@ -2477,13 +2475,7 @@ public class DeviceIdleController extends SystemService
}
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
}
- if (mLocalAlarmManager != null) {
- if (DEBUG) {
- Slog.d(TAG, "Setting alarm whitelist to "
- + Arrays.toString(mPowerSaveWhitelistUserAppIdArray));
- }
- mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
- }
+ passWhiteListToForceAppStandbyTrackerLocked();
}
private void updateTempWhitelistAppIdsLocked(int appId, boolean adding) {
@@ -2509,6 +2501,7 @@ public class DeviceIdleController extends SystemService
}
mLocalPowerManager.setDeviceIdleTempWhitelist(mTempWhitelistAppIdArray);
}
+ passWhiteListToForceAppStandbyTrackerLocked();
}
private void reportPowerSaveWhitelistChangedLocked() {
@@ -2523,6 +2516,12 @@ public class DeviceIdleController extends SystemService
getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
}
+ private void passWhiteListToForceAppStandbyTrackerLocked() {
+ ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds(
+ mPowerSaveWhitelistAllAppIdArray,
+ mTempWhitelistAppIdArray);
+ }
+
void readConfigFileLocked() {
if (DEBUG) Slog.d(TAG, "Reading config from " + mConfigFile.getBaseFile());
mPowerSaveWhitelistUserApps.clear();
diff --git a/com/android/server/ForceAppStandbyTracker.java b/com/android/server/ForceAppStandbyTracker.java
new file mode 100644
index 00000000..61d3833d
--- /dev/null
+++ b/com/android/server/ForceAppStandbyTracker.java
@@ -0,0 +1,838 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.PackageOps;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager.ServiceType;
+import android.os.PowerManagerInternal;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class to keep track of the information related to "force app standby", which includes:
+ * - OP_RUN_ANY_IN_BACKGROUND for each package
+ * - UID foreground state
+ * - User+system power save whitelist
+ * - Temporary power save whitelist
+ * - Global "force all apps standby" mode enforced by battery saver.
+ *
+ * TODO: In general, we can reduce the number of callbacks by checking all signals before sending
+ * each callback. For example, even when an UID comes into the foreground, if it wasn't
+ * originally restricted, then there's no need to send an event.
+ * Doing this would be error-prone, so we punt it for now, but we should revisit it later.
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+ */
+public class ForceAppStandbyTracker {
+ private static final String TAG = "ForceAppStandbyTracker";
+
+ @GuardedBy("ForceAppStandbyTracker.class")
+ private static ForceAppStandbyTracker sInstance;
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+
+ @VisibleForTesting
+ static final int TARGET_OP = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
+
+ IActivityManager mIActivityManager;
+ AppOpsManager mAppOpsManager;
+ IAppOpsService mAppOpsService;
+ PowerManagerInternal mPowerManagerInternal;
+
+ private final MyHandler mHandler;
+
+ /**
+ * Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed.
+ */
+ @GuardedBy("mLock")
+ final ArraySet<Pair<Integer, String>> mRunAnyRestrictedPackages = new ArraySet<>();
+
+ @GuardedBy("mLock")
+ final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
+
+ @GuardedBy("mLock")
+ private int[] mPowerWhitelistedAllAppIds = new int[0];
+
+ @GuardedBy("mLock")
+ private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds;
+
+ @GuardedBy("mLock")
+ final ArraySet<Listener> mListeners = new ArraySet<>();
+
+ @GuardedBy("mLock")
+ boolean mStarted;
+
+ @GuardedBy("mLock")
+ boolean mForceAllAppsStandby;
+
+ public static abstract class Listener {
+ /**
+ * This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package.
+ */
+ private void onRunAnyAppOpsChanged(ForceAppStandbyTracker sender,
+ int uid, @NonNull String packageName) {
+ updateJobsForUidPackage(uid, packageName);
+
+ if (!sender.areAlarmsRestricted(uid, packageName)) {
+ unblockAlarmsForUidPackage(uid, packageName);
+ }
+ }
+
+ /**
+ * This is called when the foreground state changed for a UID.
+ */
+ private void onUidForegroundStateChanged(ForceAppStandbyTracker sender, int uid) {
+ updateJobsForUid(uid);
+
+ if (sender.isInForeground(uid)) {
+ unblockAlarmsForUid(uid);
+ }
+ }
+
+ /**
+ * This is called when an app-id(s) is removed from the power save whitelist.
+ */
+ private void onPowerSaveUnwhitelisted(ForceAppStandbyTracker sender) {
+ updateAllJobs();
+ unblockAllUnrestrictedAlarms();
+ }
+
+ /**
+ * This is called when the power save whitelist changes, excluding the
+ * {@link #onPowerSaveUnwhitelisted} case.
+ */
+ private void onPowerSaveWhitelistedChanged(ForceAppStandbyTracker sender) {
+ updateAllJobs();
+ }
+
+ /**
+ * This is called when the temp whitelist changes.
+ */
+ private void onTempPowerSaveWhitelistChanged(ForceAppStandbyTracker sender) {
+
+ // TODO This case happens rather frequently; consider optimizing and update jobs
+ // only for affected app-ids.
+
+ updateAllJobs();
+ }
+
+ /**
+ * This is called when the global "force all apps standby" flag changes.
+ */
+ private void onForceAllAppsStandbyChanged(ForceAppStandbyTracker sender) {
+ updateAllJobs();
+
+ if (!sender.isForceAllAppsStandbyEnabled()) {
+ unblockAllUnrestrictedAlarms();
+ }
+ }
+
+ /**
+ * Called when the job restrictions for multiple UIDs might have changed, so the job
+ * scheduler should re-evaluate all restrictions for all jobs.
+ */
+ public void updateAllJobs() {
+ }
+
+ /**
+ * Called when the job restrictions for a UID might have changed, so the job
+ * scheduler should re-evaluate all restrictions for all jobs.
+ */
+ public void updateJobsForUid(int uid) {
+ }
+
+ /**
+ * Called when the job restrictions for a UID - package might have changed, so the job
+ * scheduler should re-evaluate all restrictions for all jobs.
+ */
+ public void updateJobsForUidPackage(int uid, String packageName) {
+ }
+
+ /**
+ * Called when the job restrictions for multiple UIDs might have changed, so the alarm
+ * manager should re-evaluate all restrictions for all blocked jobs.
+ */
+ public void unblockAllUnrestrictedAlarms() {
+ }
+
+ /**
+ * Called when all jobs for a specific UID are unblocked.
+ */
+ public void unblockAlarmsForUid(int uid) {
+ }
+
+ /**
+ * Called when all alarms for a specific UID - package are unblocked.
+ */
+ public void unblockAlarmsForUidPackage(int uid, String packageName) {
+ }
+ }
+
+ @VisibleForTesting
+ ForceAppStandbyTracker(Context context, Looper looper) {
+ mContext = context;
+ mHandler = new MyHandler(looper);
+ }
+
+ private ForceAppStandbyTracker(Context context) {
+ this(context, FgThread.get().getLooper());
+ }
+
+ /**
+ * Get the singleton instance.
+ */
+ public static synchronized ForceAppStandbyTracker getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new ForceAppStandbyTracker(context);
+ }
+ return sInstance;
+ }
+
+ /**
+ * Call it when the system is ready.
+ */
+ public void start() {
+ synchronized (mLock) {
+ if (mStarted) {
+ return;
+ }
+ mStarted = true;
+
+ mIActivityManager = Preconditions.checkNotNull(injectIActivityManager());
+ mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
+ mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
+ mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
+
+ try {
+ mIActivityManager.registerUidObserver(new UidObserver(),
+ ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
+ | ActivityManager.UID_OBSERVER_ACTIVE,
+ ActivityManager.PROCESS_STATE_UNKNOWN, null);
+ mAppOpsService.startWatchingMode(TARGET_OP, null,
+ new AppOpsWatcher());
+ } catch (RemoteException e) {
+ // shouldn't happen.
+ }
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiver(new MyReceiver(), filter);
+
+ refreshForcedAppStandbyUidPackagesLocked();
+
+ mPowerManagerInternal.registerLowPowerModeObserver(
+ ServiceType.FORCE_ALL_APPS_STANDBY,
+ (state) -> updateForceAllAppsStandby(state.batterySaverEnabled));
+
+ updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState(
+ ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled);
+ }
+ }
+
+ @VisibleForTesting
+ AppOpsManager injectAppOpsManager() {
+ return mContext.getSystemService(AppOpsManager.class);
+ }
+
+ @VisibleForTesting
+ IAppOpsService injectIAppOpsService() {
+ return IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
+ }
+
+ @VisibleForTesting
+ IActivityManager injectIActivityManager() {
+ return ActivityManager.getService();
+ }
+
+ @VisibleForTesting
+ PowerManagerInternal injectPowerManagerInternal() {
+ return LocalServices.getService(PowerManagerInternal.class);
+ }
+
+ /**
+ * Update {@link #mRunAnyRestrictedPackages} with the current app ops state.
+ */
+ private void refreshForcedAppStandbyUidPackagesLocked() {
+ mRunAnyRestrictedPackages.clear();
+ final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(
+ new int[] {TARGET_OP});
+
+ if (ops == null) {
+ return;
+ }
+ final int size = ops.size();
+ for (int i = 0; i < size; i++) {
+ final AppOpsManager.PackageOps pkg = ops.get(i);
+ final List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
+
+ for (int j = 0; j < entries.size(); j++) {
+ AppOpsManager.OpEntry ent = entries.get(j);
+ if (ent.getOp() != TARGET_OP) {
+ continue;
+ }
+ if (ent.getMode() != AppOpsManager.MODE_ALLOWED) {
+ mRunAnyRestrictedPackages.add(Pair.create(
+ pkg.getUid(), pkg.getPackageName()));
+ }
+ }
+ }
+ }
+
+ /**
+ * Update {@link #mForceAllAppsStandby} and notifies the listeners.
+ */
+ void updateForceAllAppsStandby(boolean enable) {
+ synchronized (mLock) {
+ if (enable == mForceAllAppsStandby) {
+ return;
+ }
+ mForceAllAppsStandby = enable;
+
+ mHandler.notifyForceAllAppsStandbyChanged();
+ }
+ }
+
+ private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) {
+ final int size = mRunAnyRestrictedPackages.size();
+ if (size > 8) {
+ return mRunAnyRestrictedPackages.indexOf(Pair.create(uid, packageName));
+ }
+ for (int i = 0; i < size; i++) {
+ final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
+
+ if ((pair.first == uid) && packageName.equals(pair.second)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @return whether a uid package-name pair is in mRunAnyRestrictedPackages.
+ */
+ boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) {
+ return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0;
+ }
+
+ /**
+ * Add to / remove from {@link #mRunAnyRestrictedPackages}.
+ */
+ boolean updateForcedAppStandbyUidPackageLocked(int uid, @NonNull String packageName,
+ boolean restricted) {
+ final int index = findForcedAppStandbyUidPackageIndexLocked(uid, packageName);
+ final boolean wasRestricted = index >= 0;
+ if (wasRestricted == restricted) {
+ return false;
+ }
+ if (restricted) {
+ mRunAnyRestrictedPackages.add(Pair.create(uid, packageName));
+ } else {
+ mRunAnyRestrictedPackages.removeAt(index);
+ }
+ return true;
+ }
+
+ /**
+ * Puts a UID to {@link #mForegroundUids}.
+ */
+ void uidToForeground(int uid) {
+ synchronized (mLock) {
+ if (!UserHandle.isApp(uid)) {
+ return;
+ }
+ // TODO This can be optimized by calling indexOfKey and sharing the index for get and
+ // put.
+ if (mForegroundUids.get(uid)) {
+ return;
+ }
+ mForegroundUids.put(uid, true);
+ mHandler.notifyUidForegroundStateChanged(uid);
+ }
+ }
+
+ /**
+ * Sets false for a UID {@link #mForegroundUids}, or remove it when {@code remove} is true.
+ */
+ void uidToBackground(int uid, boolean remove) {
+ synchronized (mLock) {
+ if (!UserHandle.isApp(uid)) {
+ return;
+ }
+ // TODO This can be optimized by calling indexOfKey and sharing the index for get and
+ // put.
+ if (!mForegroundUids.get(uid)) {
+ return;
+ }
+ if (remove) {
+ mForegroundUids.delete(uid);
+ } else {
+ mForegroundUids.put(uid, false);
+ }
+ mHandler.notifyUidForegroundStateChanged(uid);
+ }
+ }
+
+ private final class UidObserver extends IUidObserver.Stub {
+ @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
+ }
+
+ @Override public void onUidGone(int uid, boolean disabled) {
+ uidToBackground(uid, /*remove=*/ true);
+ }
+
+ @Override public void onUidActive(int uid) {
+ uidToForeground(uid);
+ }
+
+ @Override public void onUidIdle(int uid, boolean disabled) {
+ // Just to avoid excessive memcpy, don't remove from the array in this case.
+ uidToBackground(uid, /*remove=*/ false);
+ }
+
+ @Override public void onUidCachedChanged(int uid, boolean cached) {
+ }
+ };
+
+ private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+ @Override
+ public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ boolean restricted = false;
+ try {
+ restricted = mAppOpsService.checkOperation(TARGET_OP,
+ uid, packageName) != AppOpsManager.MODE_ALLOWED;
+ } catch (RemoteException e) {
+ // Shouldn't happen
+ }
+ synchronized (mLock) {
+ if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) {
+ mHandler.notifyRunAnyAppOpsChanged(uid, packageName);
+ }
+ }
+ }
+ }
+
+ private final class MyReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId > 0) {
+ mHandler.doUserRemoved(userId);
+ }
+ }
+ }
+ }
+
+ private Listener[] cloneListeners() {
+ synchronized (mLock) {
+ return mListeners.toArray(new Listener[mListeners.size()]);
+ }
+ }
+
+ private class MyHandler extends Handler {
+ private static final int MSG_UID_STATE_CHANGED = 1;
+ private static final int MSG_RUN_ANY_CHANGED = 2;
+ private static final int MSG_ALL_UNWHITELISTED = 3;
+ private static final int MSG_ALL_WHITELIST_CHANGED = 4;
+ private static final int MSG_TEMP_WHITELIST_CHANGED = 5;
+ private static final int MSG_FORCE_ALL_CHANGED = 6;
+
+ private static final int MSG_USER_REMOVED = 7;
+
+ public MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void notifyUidForegroundStateChanged(int uid) {
+ obtainMessage(MSG_UID_STATE_CHANGED, uid, 0).sendToTarget();
+ }
+ public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) {
+ obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget();
+ }
+
+ public void notifyAllUnwhitelisted() {
+ obtainMessage(MSG_ALL_UNWHITELISTED).sendToTarget();
+ }
+
+ public void notifyAllWhitelistChanged() {
+ obtainMessage(MSG_ALL_WHITELIST_CHANGED).sendToTarget();
+ }
+
+ public void notifyTempWhitelistChanged() {
+ obtainMessage(MSG_TEMP_WHITELIST_CHANGED).sendToTarget();
+ }
+
+ public void notifyForceAllAppsStandbyChanged() {
+ obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget();
+ }
+
+ public void doUserRemoved(int userId) {
+ obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
+ }
+
+ @Override
+ public void dispatchMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_USER_REMOVED:
+ handleUserRemoved(msg.arg1);
+ return;
+ }
+
+ // Only notify the listeners when started.
+ synchronized (mLock) {
+ if (!mStarted) {
+ return;
+ }
+ }
+ final ForceAppStandbyTracker sender = ForceAppStandbyTracker.this;
+
+ switch (msg.what) {
+ case MSG_UID_STATE_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onUidForegroundStateChanged(sender, msg.arg1);
+ }
+ return;
+ case MSG_RUN_ANY_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj);
+ }
+ return;
+ case MSG_ALL_UNWHITELISTED:
+ for (Listener l : cloneListeners()) {
+ l.onPowerSaveUnwhitelisted(sender);
+ }
+ return;
+ case MSG_ALL_WHITELIST_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onPowerSaveWhitelistedChanged(sender);
+ }
+ return;
+ case MSG_TEMP_WHITELIST_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onTempPowerSaveWhitelistChanged(sender);
+ }
+ return;
+ case MSG_FORCE_ALL_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onForceAllAppsStandbyChanged(sender);
+ }
+ return;
+ case MSG_USER_REMOVED:
+ handleUserRemoved(msg.arg1);
+ return;
+ }
+ }
+ }
+
+ void handleUserRemoved(int removedUserId) {
+ synchronized (mLock) {
+ for (int i = mRunAnyRestrictedPackages.size() - 1; i >= 0; i--) {
+ final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
+ final int uid = pair.first;
+ final int userId = UserHandle.getUserId(uid);
+
+ if (userId == removedUserId) {
+ mRunAnyRestrictedPackages.removeAt(i);
+ }
+ }
+ for (int i = mForegroundUids.size() - 1; i >= 0; i--) {
+ final int uid = mForegroundUids.keyAt(i);
+ final int userId = UserHandle.getUserId(uid);
+
+ if (userId == removedUserId) {
+ mForegroundUids.removeAt(i);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called by device idle controller to update the power save whitelists.
+ */
+ public void setPowerSaveWhitelistAppIds(
+ int[] powerSaveWhitelistAllAppIdArray, int[] tempWhitelistAppIdArray) {
+ synchronized (mLock) {
+ final int[] previousWhitelist = mPowerWhitelistedAllAppIds;
+ final int[] previousTempWhitelist = mTempWhitelistedAppIds;
+
+ mPowerWhitelistedAllAppIds = powerSaveWhitelistAllAppIdArray;
+ mTempWhitelistedAppIds = tempWhitelistAppIdArray;
+
+ if (isAnyAppIdUnwhitelisted(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+ mHandler.notifyAllUnwhitelisted();
+ } else if (!Arrays.equals(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+ mHandler.notifyAllWhitelistChanged();
+ }
+
+ if (!Arrays.equals(previousTempWhitelist, mTempWhitelistedAppIds)) {
+ mHandler.notifyTempWhitelistChanged();
+ }
+
+ }
+ }
+
+ /**
+ * @retunr true if a sorted app-id array {@code prevArray} has at least one element
+ * that's not in a sorted app-id array {@code newArray}.
+ */
+ @VisibleForTesting
+ static boolean isAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray) {
+ int i1 = 0;
+ int i2 = 0;
+ boolean prevFinished;
+ boolean newFinished;
+
+ for (;;) {
+ prevFinished = i1 >= prevArray.length;
+ newFinished = i2 >= newArray.length;
+ if (prevFinished || newFinished) {
+ break;
+ }
+ int a1 = prevArray[i1];
+ int a2 = newArray[i2];
+
+ if (a1 == a2) {
+ i1++;
+ i2++;
+ continue;
+ }
+ if (a1 < a2) {
+ // prevArray has an element that's not in a2.
+ return true;
+ }
+ i2++;
+ }
+ if (prevFinished) {
+ return false;
+ }
+ return newFinished;
+ }
+
+ // Public interface.
+
+ /**
+ * Register a new listener.
+ */
+ public void addListener(@NonNull Listener listener) {
+ synchronized (mLock) {
+ mListeners.add(listener);
+ }
+ }
+
+ /**
+ * @return whether alarms should be restricted for a UID package-name.
+ */
+ public boolean areAlarmsRestricted(int uid, @NonNull String packageName) {
+ return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false);
+ }
+
+ /**
+ * @return whether jobs should be restricted for a UID package-name.
+ */
+ public boolean areJobsRestricted(int uid, @NonNull String packageName) {
+ return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true);
+ }
+
+ /**
+ * @return whether force-app-standby is effective for a UID package-name.
+ */
+ private boolean isRestricted(int uid, @NonNull String packageName,
+ boolean useTempWhitelistToo) {
+ if (isInForeground(uid)) {
+ return false;
+ }
+ synchronized (mLock) {
+ // Whitelisted?
+ final int appId = UserHandle.getAppId(uid);
+ if (ArrayUtils.contains(mPowerWhitelistedAllAppIds, appId)) {
+ return false;
+ }
+ if (useTempWhitelistToo &&
+ ArrayUtils.contains(mTempWhitelistedAppIds, appId)) {
+ return false;
+ }
+
+ if (mForceAllAppsStandby) {
+ return true;
+ }
+
+ return isRunAnyRestrictedLocked(uid, packageName);
+ }
+ }
+
+ /**
+ * @return whether a UID is in the foreground or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
+ public boolean isInForeground(int uid) {
+ if (!UserHandle.isApp(uid)) {
+ return true;
+ }
+ synchronized (mLock) {
+ return mForegroundUids.get(uid);
+ }
+ }
+
+ /**
+ * @return whether force all apps standby is enabled or not.
+ *
+ * Note clients normally shouldn't need to access it.
+ */
+ boolean isForceAllAppsStandbyEnabled() {
+ synchronized (mLock) {
+ return mForceAllAppsStandby;
+ }
+ }
+
+ /**
+ * @return whether a UID/package has {@code OP_RUN_ANY_IN_BACKGROUND} allowed or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
+ public boolean isRunAnyInBackgroundAppOpsAllowed(int uid, @NonNull String packageName) {
+ synchronized (mLock) {
+ return !isRunAnyRestrictedLocked(uid, packageName);
+ }
+ }
+
+ /**
+ * @return whether a UID is in the user / system defined power-save whitelist or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
+ public boolean isUidPowerSaveWhitelisted(int uid) {
+ synchronized (mLock) {
+ return ArrayUtils.contains(mPowerWhitelistedAllAppIds, UserHandle.getAppId(uid));
+ }
+ }
+
+ /**
+ * @return whether a UID is in the temp power-save whitelist or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
+ public boolean isUidTempPowerSaveWhitelisted(int uid) {
+ synchronized (mLock) {
+ return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid));
+ }
+ }
+
+ public void dump(PrintWriter pw, String indent) {
+ synchronized (mLock) {
+ pw.print(indent);
+ pw.print("Force all apps standby: ");
+ pw.println(isForceAllAppsStandbyEnabled());
+
+ pw.print(indent);
+ pw.print("Foreground uids: [");
+
+ String sep = "";
+ for (int i = 0; i < mForegroundUids.size(); i++) {
+ if (mForegroundUids.valueAt(i)) {
+ pw.print(sep);
+ pw.print(UserHandle.formatUid(mForegroundUids.keyAt(i)));
+ sep = " ";
+ }
+ }
+ pw.println("]");
+
+ pw.print(indent);
+ pw.print("Whitelist appids: ");
+ pw.println(Arrays.toString(mPowerWhitelistedAllAppIds));
+
+ pw.print(indent);
+ pw.print("Temp whitelist appids: ");
+ pw.println(Arrays.toString(mTempWhitelistedAppIds));
+
+ pw.print(indent);
+ pw.println("Restricted packages:");
+ for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+ pw.print(indent);
+ pw.print(" ");
+ pw.print(UserHandle.formatUid(uidAndPackage.first));
+ pw.print(" ");
+ pw.print(uidAndPackage.second);
+ pw.println();
+ }
+ }
+ }
+
+ public void dumpProto(ProtoOutputStream proto, long fieldId) {
+ synchronized (mLock) {
+ final long token = proto.start(fieldId);
+
+ proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby);
+
+ for (int i = 0; i < mForegroundUids.size(); i++) {
+ if (mForegroundUids.valueAt(i)) {
+ proto.write(ForceAppStandbyTrackerProto.FOREGROUND_UIDS,
+ mForegroundUids.keyAt(i));
+ }
+ }
+
+ for (int appId : mPowerWhitelistedAllAppIds) {
+ proto.write(ForceAppStandbyTrackerProto.POWER_SAVE_WHITELIST_APP_IDS, appId);
+ }
+
+ for (int appId : mTempWhitelistedAppIds) {
+ proto.write(ForceAppStandbyTrackerProto.TEMP_POWER_SAVE_WHITELIST_APP_IDS, appId);
+ }
+
+ for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+ final long token2 = proto.start(
+ ForceAppStandbyTrackerProto.RUN_ANY_IN_BACKGROUND_RESTRICTED_PACKAGES);
+ proto.write(RunAnyInBackgroundRestrictedPackages.UID, uidAndPackage.first);
+ proto.write(RunAnyInBackgroundRestrictedPackages.PACKAGE_NAME,
+ uidAndPackage.second);
+ proto.end(token2);
+ }
+ proto.end(token);
+ }
+ }
+}
diff --git a/com/android/server/GestureLauncherService.java b/com/android/server/GestureLauncherService.java
index a903f3df..b7b5bd93 100644
--- a/com/android/server/GestureLauncherService.java
+++ b/com/android/server/GestureLauncherService.java
@@ -40,13 +40,13 @@ import android.provider.Settings;
import android.util.MutableBoolean;
import android.util.Slog;
import android.view.KeyEvent;
-import android.view.WindowManagerInternal;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
/**
* The service that listens for gestures detected in sensor firmware and starts the intent
diff --git a/com/android/server/InputMethodManagerService.java b/com/android/server/InputMethodManagerService.java
index f007bcc8..fc57a0d5 100644
--- a/com/android/server/InputMethodManagerService.java
+++ b/com/android/server/InputMethodManagerService.java
@@ -128,7 +128,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
-import android.view.WindowManagerInternal;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
@@ -147,6 +146,8 @@ import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
+import com.android.server.wm.WindowManagerInternal;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
diff --git a/com/android/server/LocationManagerService.java b/com/android/server/LocationManagerService.java
index 0fd59eaa..bdfccd6e 100644
--- a/com/android/server/LocationManagerService.java
+++ b/com/android/server/LocationManagerService.java
@@ -118,7 +118,7 @@ public class LocationManagerService extends ILocationManager.Stub {
private static final String TAG = "LocationManagerService";
public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
- private static final String WAKELOCK_KEY = TAG;
+ private static final String WAKELOCK_KEY = "*location*";
// Location resolution level: no location data whatsoever
private static final int RESOLUTION_LEVEL_NONE = 0;
diff --git a/com/android/server/NetworkTimeUpdateService.java b/com/android/server/NetworkTimeUpdateService.java
index 1ad14047..2c247982 100644
--- a/com/android/server/NetworkTimeUpdateService.java
+++ b/com/android/server/NetworkTimeUpdateService.java
@@ -69,13 +69,10 @@ public class NetworkTimeUpdateService extends Binder {
private static final String ACTION_POLL =
"com.android.server.NetworkTimeUpdateService.action.POLL";
- private static final int NETWORK_CHANGE_EVENT_DELAY_MS = 1000;
- private static int POLL_REQUEST = 0;
+ private static final int POLL_REQUEST = 0;
private static final long NOT_SET = -1;
private long mNitzTimeSetTime = NOT_SET;
- // TODO: Have a way to look up the timezone we are in
- private long mNitzZoneSetTime = NOT_SET;
private Network mDefaultNetwork = null;
private Context mContext;
@@ -144,7 +141,6 @@ public class NetworkTimeUpdateService extends Binder {
private void registerForTelephonyIntents() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
- intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
mContext.registerReceiver(mNitzReceiver, intentFilter);
}
@@ -257,8 +253,6 @@ public class NetworkTimeUpdateService extends Binder {
if (DBG) Log.d(TAG, "Received " + action);
if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
mNitzTimeSetTime = SystemClock.elapsedRealtime();
- } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
- mNitzZoneSetTime = SystemClock.elapsedRealtime();
}
}
};
diff --git a/com/android/server/StorageManagerService.java b/com/android/server/StorageManagerService.java
index 3c955eb0..66b3adb6 100644
--- a/com/android/server/StorageManagerService.java
+++ b/com/android/server/StorageManagerService.java
@@ -2192,6 +2192,11 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
+ if (StorageManager.isFileEncryptedNativeOnly()) {
+ // Not supported on FBE devices
+ return -1;
+ }
+
if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
password = "";
} else if (TextUtils.isEmpty(password)) {
@@ -2268,6 +2273,11 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
+ if (StorageManager.isFileEncryptedNativeOnly()) {
+ // Not supported on FBE devices
+ return;
+ }
+
try {
mVold.fdeSetField(field, contents);
return;
@@ -2287,6 +2297,11 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
+ if (StorageManager.isFileEncryptedNativeOnly()) {
+ // Not supported on FBE devices
+ return null;
+ }
+
try {
return mVold.fdeGetField(field);
} catch (Exception e) {
@@ -2568,7 +2583,7 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
}
@Override
- public int mkdirs(String callingPkg, String appPath) {
+ public void mkdirs(String callingPkg, String appPath) {
final int userId = UserHandle.getUserId(Binder.getCallingUid());
final UserEnvironment userEnv = new UserEnvironment(userId);
@@ -2581,8 +2596,7 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
try {
appFile = new File(appPath).getCanonicalFile();
} catch (IOException e) {
- Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
- return -1;
+ throw new IllegalStateException("Failed to resolve " + appPath + ": " + e);
}
// Try translating the app path into a vold path, but require that it
@@ -2597,9 +2611,8 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
try {
mVold.mkdirs(appPath);
- return 0;
} catch (Exception e) {
- Slog.wtf(TAG, e);
+ throw new IllegalStateException("Failed to prepare " + appPath + ": " + e);
}
}
diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java
index 74a7bd4a..33f4e348 100644
--- a/com/android/server/SystemServer.java
+++ b/com/android/server/SystemServer.java
@@ -35,6 +35,7 @@ import android.os.FileUtils;
import android.os.IIncidentManager;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
import android.os.PowerManager;
import android.os.Process;
import android.os.ServiceManager;
@@ -360,6 +361,9 @@ public final class SystemServer {
// to avoid throwing BadParcelableException.
BaseBundle.setShouldDefuse(true);
+ // Within the system server, when parceling exceptions, include the stack trace
+ Parcel.setStackTraceParceling(true);
+
// Ensure binder calls into the system always run at foreground priority.
BinderInternal.disableBackgroundScheduling(true);
diff --git a/com/android/server/accessibility/AccessibilityClientConnection.java b/com/android/server/accessibility/AccessibilityClientConnection.java
index df4c8ed5..22d922be 100644
--- a/com/android/server/accessibility/AccessibilityClientConnection.java
+++ b/com/android/server/accessibility/AccessibilityClientConnection.java
@@ -20,9 +20,7 @@ import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
-import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.NonNull;
@@ -47,10 +45,8 @@ import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.View;
-import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityCache;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -60,11 +56,13 @@ import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy;
+import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -106,7 +104,7 @@ abstract class AccessibilityClientConnection extends IAccessibilityServiceConnec
int mFeedbackType;
- Set<String> mPackageNames = new HashSet<>();
+ final Set<String> mPackageNames = new HashSet<>();
boolean mIsDefault;
@@ -284,40 +282,98 @@ abstract class AccessibilityClientConnection extends IAccessibilityServiceConnec
return true;
}
- public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
- mEventTypes = info.eventTypes;
- mFeedbackType = info.feedbackType;
- String[] packageNames = info.packageNames;
- if (packageNames != null) {
- mPackageNames.addAll(Arrays.asList(packageNames));
+ boolean setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
+ boolean somethingChanged = false;
+
+ if (mEventTypes != info.eventTypes) {
+ mEventTypes = info.eventTypes;
+ somethingChanged = true;
}
- mNotificationTimeout = info.notificationTimeout;
- mIsDefault = (info.flags & DEFAULT) != 0;
- if (supportsFlagForNotImportantViews(info)) {
- if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- } else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ if (mFeedbackType != info.feedbackType) {
+ mFeedbackType = info.feedbackType;
+ somethingChanged = true;
+ }
+
+ final String[] oldPackageNames = mPackageNames.toArray(new String[mPackageNames.size()]);
+ if (!Arrays.equals(oldPackageNames, info.packageNames)) {
+ mPackageNames.clear();
+ if (info.packageNames != null) {
+ Collections.addAll(mPackageNames, info.packageNames);
}
+ somethingChanged = true;
}
- if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
- } else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
+ if (mNotificationTimeout != info.notificationTimeout) {
+ mNotificationTimeout = info.notificationTimeout;
+ somethingChanged = true;
+ }
+
+ final boolean newIsDefault = (info.flags & DEFAULT) != 0;
+ if (mIsDefault != newIsDefault) {
+ mIsDefault = newIsDefault;
+ somethingChanged = true;
}
- mRequestTouchExplorationMode = (info.flags
+ if (supportsFlagForNotImportantViews(info)) {
+ somethingChanged |= updateFetchFlag(info.flags,
+ AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS);
+ }
+
+ somethingChanged |= updateFetchFlag(info.flags,
+ AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS);
+
+ final boolean newRequestTouchExplorationMode = (info.flags
& AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
- mRequestFilterKeyEvents = (info.flags
+ if (mRequestTouchExplorationMode != newRequestTouchExplorationMode) {
+ mRequestTouchExplorationMode = newRequestTouchExplorationMode;
+ somethingChanged = true;
+ }
+
+ final boolean newRequestFilterKeyEvents = (info.flags
& AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
- mRetrieveInteractiveWindows = (info.flags
+ if (mRequestFilterKeyEvents != newRequestFilterKeyEvents) {
+ mRequestFilterKeyEvents = newRequestFilterKeyEvents;
+ somethingChanged = true;
+ }
+
+ final boolean newRetrieveInteractiveWindows = (info.flags
& AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
- mCaptureFingerprintGestures = (info.flags
+ if (mRetrieveInteractiveWindows != newRetrieveInteractiveWindows) {
+ mRetrieveInteractiveWindows = newRetrieveInteractiveWindows;
+ somethingChanged = true;
+ }
+
+ final boolean newCaptureFingerprintGestures = (info.flags
& AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
- mRequestAccessibilityButton = (info.flags
+ if (mCaptureFingerprintGestures != newCaptureFingerprintGestures) {
+ mCaptureFingerprintGestures = newCaptureFingerprintGestures;
+ somethingChanged = true;
+ }
+
+ final boolean newRequestAccessibilityButton = (info.flags
& AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+ if (mRequestAccessibilityButton != newRequestAccessibilityButton) {
+ mRequestAccessibilityButton = newRequestAccessibilityButton;
+ somethingChanged = true;
+ }
+
+ return somethingChanged;
+ }
+
+ private boolean updateFetchFlag(int allFlags, int flagToUpdate) {
+ if ((allFlags & flagToUpdate) != 0) {
+ if ((mFetchFlags & flagToUpdate) == 0) {
+ mFetchFlags |= flagToUpdate;
+ return true;
+ }
+ } else {
+ if ((mFetchFlags & flagToUpdate) != 0) {
+ mFetchFlags &= ~flagToUpdate;
+ return true;
+ }
+ }
+ return false;
}
protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
@@ -349,14 +405,15 @@ abstract class AccessibilityClientConnection extends IAccessibilityServiceConnec
// If the XML manifest had data to configure the service its info
// should be already set. In such a case update only the dynamically
// configurable properties.
+ final boolean serviceInfoChanged;
AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo;
if (oldInfo != null) {
oldInfo.updateDynamicallyConfigurableProperties(info);
- setDynamicallyConfigurableProperties(oldInfo);
+ serviceInfoChanged = setDynamicallyConfigurableProperties(oldInfo);
} else {
- setDynamicallyConfigurableProperties(info);
+ serviceInfoChanged = setDynamicallyConfigurableProperties(info);
}
- mSystemSupport.onClientChange(true);
+ mSystemSupport.onClientChange(serviceInfoChanged);
}
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/com/android/server/accessibility/AccessibilityInputFilter.java b/com/android/server/accessibility/AccessibilityInputFilter.java
index f6fcaae4..11b2343d 100644
--- a/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -31,11 +31,11 @@ import android.view.InputEvent;
import android.view.InputFilter;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.util.BitUtils;
import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy;
/**
* This class is an input filter for implementing accessibility features such
diff --git a/com/android/server/accessibility/AccessibilityManagerService.java b/com/android/server/accessibility/AccessibilityManagerService.java
index d661754a..8b5c85a7 100644
--- a/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/com/android/server/accessibility/AccessibilityManagerService.java
@@ -82,7 +82,6 @@ import android.view.MagnificationSpec;
import android.view.View;
import android.view.WindowInfo;
import android.view.WindowManager;
-import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityCache;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
@@ -102,6 +101,7 @@ import com.android.internal.util.DumpUtils;
import com.android.internal.util.IntPair;
import com.android.server.LocalServices;
import com.android.server.policy.AccessibilityShortcutController;
+import com.android.server.wm.WindowManagerInternal;
import org.xmlpull.v1.XmlPullParserException;
@@ -2400,7 +2400,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private void announceNewUserIfNeeded() {
synchronized (mLock) {
UserState userState = getCurrentUserStateLocked();
- if (userState.isHandlingAccessibilityEvents()) {
+ if (userState.isHandlingAccessibilityEvents()
+ && userState.isObservedEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT)) {
UserManager userManager = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
String message = mContext.getString(R.string.user_switched,
@@ -3157,13 +3158,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (mWindowsForAccessibilityCallback == null) {
return;
}
+ final int userId;
+ synchronized (mLock) {
+ userId = mCurrentUserId;
+ final UserState userState = getUserStateLocked(userId);
+ if (!userState.isObservedEventType(AccessibilityEvent.TYPE_WINDOWS_CHANGED)) {
+ return;
+ }
+ }
final long identity = Binder.clearCallingIdentity();
try {
// Let the client know the windows changed.
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_WINDOWS_CHANGED);
event.setEventTime(SystemClock.uptimeMillis());
- sendAccessibilityEvent(event, mCurrentUserId);
+ sendAccessibilityEvent(event, userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -3368,6 +3377,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mUserId = userId;
}
+ public boolean isObservedEventType(@AccessibilityEvent.EventType int type) {
+ return (mLastSentRelevantEventTypes & type) != 0;
+ }
+
public int getClientState() {
int clientState = 0;
final boolean a11yEnabled = (mUiAutomationManager.isUiAutomationRunningLocked()
diff --git a/com/android/server/accessibility/AccessibilityServiceConnection.java b/com/android/server/accessibility/AccessibilityServiceConnection.java
index eb267529..9cafa1e7 100644
--- a/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -33,10 +33,10 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
-import android.view.WindowManagerInternal;
import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy;
import com.android.server.accessibility.AccessibilityManagerService.UserState;
+import com.android.server.wm.WindowManagerInternal;
import java.lang.ref.WeakReference;
import java.util.List;
diff --git a/com/android/server/accessibility/GlobalActionPerformer.java b/com/android/server/accessibility/GlobalActionPerformer.java
index 5db6f7da..3b8d4bca 100644
--- a/com/android/server/accessibility/GlobalActionPerformer.java
+++ b/com/android/server/accessibility/GlobalActionPerformer.java
@@ -21,14 +21,18 @@ import android.app.StatusBarManager;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Binder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
+import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import android.view.WindowManagerInternal;
import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
/**
* Handle the back-end of AccessibilityService#performGlobalAction
@@ -72,6 +76,9 @@ public class GlobalActionPerformer {
case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: {
return toggleSplitScreen();
}
+ case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN: {
+ return lockScreen();
+ }
}
return false;
} finally {
@@ -153,4 +160,11 @@ public class GlobalActionPerformer {
}
return true;
}
+
+ private boolean lockScreen() {
+ mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(),
+ PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0);
+ mWindowManagerService.lockNow();
+ return true;
+ }
}
diff --git a/com/android/server/accessibility/KeyEventDispatcher.java b/com/android/server/accessibility/KeyEventDispatcher.java
index 33584326..b144e1c8 100644
--- a/com/android/server/accessibility/KeyEventDispatcher.java
+++ b/com/android/server/accessibility/KeyEventDispatcher.java
@@ -26,7 +26,8 @@ import android.util.Pools.Pool;
import android.util.Slog;
import android.view.InputEventConsistencyVerifier;
import android.view.KeyEvent;
-import android.view.WindowManagerPolicy;
+
+import com.android.server.policy.WindowManagerPolicy;
import java.util.ArrayList;
import java.util.List;
diff --git a/com/android/server/accessibility/KeyboardInterceptor.java b/com/android/server/accessibility/KeyboardInterceptor.java
index 77249452..bc379c20 100644
--- a/com/android/server/accessibility/KeyboardInterceptor.java
+++ b/com/android/server/accessibility/KeyboardInterceptor.java
@@ -22,7 +22,8 @@ import android.os.SystemClock;
import android.util.Pools;
import android.util.Slog;
import android.view.KeyEvent;
-import android.view.WindowManagerPolicy;
+
+import com.android.server.policy.WindowManagerPolicy;
/**
* Intercepts key events and forwards them to accessibility manager service.
diff --git a/com/android/server/accessibility/MagnificationController.java b/com/android/server/accessibility/MagnificationController.java
index a10b7a20..a70b88e8 100644
--- a/com/android/server/accessibility/MagnificationController.java
+++ b/com/android/server/accessibility/MagnificationController.java
@@ -34,7 +34,6 @@ import android.util.MathUtils;
import android.util.Slog;
import android.view.MagnificationSpec;
import android.view.View;
-import android.view.WindowManagerInternal;
import android.view.animation.DecelerateInterpolator;
import com.android.internal.R;
@@ -42,6 +41,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
import java.util.Locale;
diff --git a/com/android/server/accessibility/MotionEventInjector.java b/com/android/server/accessibility/MotionEventInjector.java
index b6b78129..46e3226b 100644
--- a/com/android/server/accessibility/MotionEventInjector.java
+++ b/com/android/server/accessibility/MotionEventInjector.java
@@ -31,9 +31,9 @@ import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.InputDevice;
import android.view.MotionEvent;
-import android.view.WindowManagerPolicy;
import com.android.internal.os.SomeArgs;
+import com.android.server.policy.WindowManagerPolicy;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/com/android/server/accessibility/TouchExplorer.java b/com/android/server/accessibility/TouchExplorer.java
index a32686df..62017e87 100644
--- a/com/android/server/accessibility/TouchExplorer.java
+++ b/com/android/server/accessibility/TouchExplorer.java
@@ -26,11 +26,12 @@ import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.ViewConfiguration;
-import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import com.android.server.policy.WindowManagerPolicy;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -790,7 +791,7 @@ class TouchExplorer extends BaseEventStreamTransformation
*/
private void sendAccessibilityEvent(int type) {
AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
- if (accessibilityManager.isEnabled()) {
+ if (accessibilityManager.isObservedEventType(type)) {
AccessibilityEvent event = AccessibilityEvent.obtain(type);
event.setWindowId(mAms.getActiveWindowId());
accessibilityManager.sendAccessibilityEvent(event);
diff --git a/com/android/server/accessibility/UiAutomationManager.java b/com/android/server/accessibility/UiAutomationManager.java
index a6b81fff..f0571126 100644
--- a/com/android/server/accessibility/UiAutomationManager.java
+++ b/com/android/server/accessibility/UiAutomationManager.java
@@ -26,9 +26,10 @@ import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.Slog;
-import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityEvent;
+import com.android.server.wm.WindowManagerInternal;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/com/android/server/am/ActiveServices.java b/com/android/server/am/ActiveServices.java
index 2131731d..3cd2f6ae 100644
--- a/com/android/server/am/ActiveServices.java
+++ b/com/android/server/am/ActiveServices.java
@@ -58,6 +58,7 @@ import com.android.internal.os.TransferPipe;
import com.android.internal.util.FastPrintWriter;
import com.android.server.am.ActivityManagerService.ItemMatcher;
import com.android.server.am.ActivityManagerService.NeededUriGrants;
+import com.android.server.am.proto.ActiveServicesProto;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -85,6 +86,7 @@ import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
import android.webkit.WebViewZygote;
public final class ActiveServices {
@@ -633,7 +635,7 @@ public final class ActiveServices {
sb.append("Stopping service due to app idle: ");
UserHandle.formatUid(sb, service.appInfo.uid);
sb.append(" ");
- TimeUtils.formatDuration(service.createTime
+ TimeUtils.formatDuration(service.createRealTime
- SystemClock.elapsedRealtime(), sb);
sb.append(" ");
sb.append(compName);
@@ -1043,8 +1045,8 @@ public final class ActiveServices {
try {
if (AppGlobals.getPackageManager().checkPermission(
android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
- r.appInfo.packageName,
- r.appInfo.uid) != PackageManager.PERMISSION_GRANTED) {
+ r.appInfo.packageName, UserHandle.getUserId(r.appInfo.uid))
+ != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Instant app " + r.appInfo.packageName
+ " does not have permission to create foreground"
+ "services");
@@ -3220,7 +3222,7 @@ public final class ActiveServices {
info.uid = r.appInfo.uid;
info.process = r.processName;
info.foreground = r.isForeground;
- info.activeSince = r.createTime;
+ info.activeSince = r.createRealTime;
info.started = r.startRequested;
info.clientCount = r.connections.size();
info.crashCount = r.crashCount;
@@ -3574,7 +3576,7 @@ public final class ActiveServices {
pw.print(" app=");
pw.println(r.app);
pw.print(" created=");
- TimeUtils.formatDuration(r.createTime, nowReal, pw);
+ TimeUtils.formatDuration(r.createRealTime, nowReal, pw);
pw.print(" started=");
pw.print(r.startRequested);
pw.print(" connections=");
@@ -3840,6 +3842,26 @@ public final class ActiveServices {
return new ServiceDumper(fd, pw, args, opti, dumpAll, dumpPackage);
}
+ protected void writeToProto(ProtoOutputStream proto) {
+ synchronized (mAm) {
+ int[] users = mAm.mUserController.getUsers();
+ for (int user : users) {
+ ServiceMap smap = mServiceMap.get(user);
+ if (smap == null) {
+ continue;
+ }
+ long token = proto.start(ActiveServicesProto.SERVICES_BY_USERS);
+ proto.write(ActiveServicesProto.ServicesByUser.USER_ID, user);
+ ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
+ for (int i=0; i<alls.size(); i++) {
+ alls.valueAt(i).writeToProto(proto,
+ ActiveServicesProto.ServicesByUser.SERVICE_RECORDS);
+ }
+ proto.end(token);
+ }
+ }
+ }
+
/**
* There are three ways to call this:
* - no service specified: dump all the services
diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java
index 2289f857..b11b16e1 100644
--- a/com/android/server/am/ActivityDisplay.java
+++ b/com/android/server/am/ActivityDisplay.java
@@ -42,6 +42,8 @@ import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.WindowConfiguration;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.util.IntArray;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -89,6 +91,9 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
private ActivityStack mPinnedStack = null;
private ActivityStack mSplitScreenPrimaryStack = null;
+ // Used in updating the display size
+ private Point mTmpDisplaySize = new Point();
+
ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
mSupervisor = supervisor;
mDisplayId = displayId;
@@ -97,6 +102,13 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
throw new IllegalStateException("Display does not exist displayId=" + displayId);
}
mDisplay = display;
+
+ updateBounds();
+ }
+
+ void updateBounds() {
+ mDisplay.getSize(mTmpDisplaySize);
+ setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
}
void addChild(ActivityStack stack, int position) {
@@ -387,6 +399,16 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
}
} finally {
+ if (mHomeStack != null && !isTopStack(mHomeStack)) {
+ // Whenever split-screen is dismissed we want the home stack directly behind the
+ // currently top stack so it shows up when the top stack is finished.
+ final ActivityStack topStack = getTopStack();
+ // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
+ // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
+ // once we have that.
+ mHomeStack.moveToFront("onSplitScreenModeDismissed");
+ topStack.moveToFront("onSplitScreenModeDismissed");
+ }
mSupervisor.mWindowManager.continueSurfaceLayout();
}
}
diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java
index 43618564..fe992daf 100644
--- a/com/android/server/am/ActivityManagerService.java
+++ b/com/android/server/am/ActivityManagerService.java
@@ -246,6 +246,7 @@ import android.app.admin.DevicePolicyManager;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.IBackupManager;
+import android.app.servertransaction.ConfigurationChangeItem;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
@@ -317,6 +318,7 @@ import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteCallbackList;
@@ -351,7 +353,6 @@ import android.util.AtomicFile;
import android.util.StatsLog;
import android.util.TimingsTraceLog;
import android.util.DebugUtils;
-import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
@@ -415,6 +416,8 @@ 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.GrantUriProto;
+import com.android.server.am.proto.NeededUriGrantsProto;
import com.android.server.am.proto.StickyBroadcastProto;
import com.android.server.firewall.IntentFirewall;
import com.android.server.job.JobSchedulerInternal;
@@ -630,6 +633,8 @@ public class ActivityManagerService extends IActivityManager.Stub
final ActivityStarter mActivityStarter;
+ final ClientLifecycleManager mLifecycleManager;
+
final TaskChangeNotificationController mTaskChangeNotificationController;
final InstrumentationReporter mInstrumentationReporter = new InstrumentationReporter();
@@ -712,8 +717,16 @@ public class ActivityManagerService extends IActivityManager.Stub
final UserController mUserController;
+ /**
+ * Packages that are being allowed to perform unrestricted app switches. Mapping is
+ * User -> Type -> uid.
+ */
+ final SparseArray<ArrayMap<String, Integer>> mAllowAppSwitchUids = new SparseArray<>();
+
final AppErrors mAppErrors;
+ final AppWarnings mAppWarnings;
+
/**
* Dump of the activity state at the time of the last ANR. Cleared after
* {@link WindowManagerService#LAST_ANR_LIFETIME_DURATION_MSECS}
@@ -1191,6 +1204,13 @@ public class ActivityManagerService extends IActivityManager.Stub
return result;
}
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(GrantUriProto.URI, uri.toString());
+ proto.write(GrantUriProto.SOURCE_USER_ID, sourceUserId);
+ proto.end(token);
+ }
+
public static GrantUri resolve(int defaultSourceUserHandle, Uri uri) {
return new GrantUri(ContentProvider.getUserIdFromUri(uri, defaultSourceUserHandle),
ContentProvider.getUriWithoutUserId(uri), false);
@@ -1717,7 +1737,6 @@ public class ActivityManagerService extends IActivityManager.Stub
static final int IDLE_UIDS_MSG = 58;
static final int LOG_STACK_STATE = 60;
static final int VR_MODE_CHANGE_MSG = 61;
- static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 62;
static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63;
static final int NOTIFY_VR_SLEEPING_MSG = 65;
static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66;
@@ -1736,7 +1755,6 @@ public class ActivityManagerService extends IActivityManager.Stub
static KillHandler sKillHandler = null;
CompatModeDialog mCompatModeDialog;
- UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
long mLastMemUsageReportTime = 0;
/**
@@ -1764,6 +1782,11 @@ public class ActivityManagerService extends IActivityManager.Stub
final boolean mPermissionReviewRequired;
+ /**
+ * Whether to force background check on all apps (for battery saver) or not.
+ */
+ boolean mForceBackgroundCheck;
+
private static String sTheRealBuildSerial = Build.UNKNOWN;
/**
@@ -1918,23 +1941,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
break;
}
- case SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG: {
- synchronized (ActivityManagerService.this) {
- final ActivityRecord ar = (ActivityRecord) msg.obj;
- if (mUnsupportedDisplaySizeDialog != null) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
- }
- if (ar != null && mCompatModePackages.getPackageNotifyUnsupportedZoomLocked(
- ar.packageName)) {
- // TODO(multi-display): Show dialog on appropriate display.
- mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
- ActivityManagerService.this, mUiContext, ar.info.applicationInfo);
- mUnsupportedDisplaySizeDialog.show();
- }
- }
- break;
- }
case DISMISS_DIALOG_UI_MSG: {
final Dialog d = (Dialog) msg.obj;
d.dismiss();
@@ -2503,7 +2509,7 @@ public class ActivityManagerService extends IActivityManager.Stub
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false,
- DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+ DUMP_FLAG_PRIORITY_HIGH);
ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
ServiceManager.addService("dbinfo", new DbBinder(this));
if (MONITOR_CPU_USAGE) {
@@ -2561,9 +2567,15 @@ public class ActivityManagerService extends IActivityManager.Stub
private final PriorityDump.PriorityDumper mPriorityDumper =
new PriorityDump.PriorityDumper() {
@Override
- public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args,
+ public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args,
boolean asProto) {
if (asProto) return;
+ mActivityManagerService.dumpApplicationMemoryUsage(fd,
+ pw, " ", new String[] {"-a"}, false, null);
+ }
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ if (asProto) return;
mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, " ", args, false, null);
}
};
@@ -2667,6 +2679,7 @@ public class ActivityManagerService extends IActivityManager.Stub
GL_ES_VERSION = 0;
mActivityStarter = null;
mAppErrors = null;
+ mAppWarnings = null;
mAppOpsService = mInjector.getAppOpsService(null, null);
mBatteryStatsService = null;
mCompatModePackages = null;
@@ -2689,6 +2702,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mUserController = null;
mVrController = null;
mLockTaskController = null;
+ mLifecycleManager = null;
}
// Note: This method is invoked on the main thread but may need to attach various
@@ -2734,10 +2748,13 @@ public class ActivityManagerService extends IActivityManager.Stub
mProviderMap = new ProviderMap(this);
mAppErrors = new AppErrors(mUiContext, this);
- // TODO: Move creation of battery stats service outside of activity manager service.
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
systemDir.mkdirs();
+
+ mAppWarnings = new AppWarnings(this, mUiContext, mHandler, mUiHandler, systemDir);
+
+ // TODO: Move creation of battery stats service outside of activity manager service.
mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, mHandler);
mBatteryStatsService.getActiveStatistics().readLocked();
mBatteryStatsService.scheduleWriteToDisk();
@@ -2784,10 +2801,11 @@ public class ActivityManagerService extends IActivityManager.Stub
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
mTaskChangeNotificationController =
new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
- mActivityStarter = new ActivityStarter(this, AppGlobals.getPackageManager());
+ mActivityStarter = new ActivityStarter(this);
mRecentTasks = createRecentTasks();
mStackSupervisor.setRecentTasks(mRecentTasks);
mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
+ mLifecycleManager = new ClientLifecycleManager();
mProcessCpuThread = new Thread("CpuTracker") {
@Override
@@ -2871,6 +2889,7 @@ public class ActivityManagerService extends IActivityManager.Stub
void onUserStoppedLocked(int userId) {
mRecentTasks.unloadUserDataFromMemoryLocked(userId);
+ mAllowAppSwitchUids.remove(userId);
}
public void initPowerManagement() {
@@ -3301,22 +3320,25 @@ public class ActivityManagerService extends IActivityManager.Stub
mUiHandler.sendMessage(msg);
}
- final void showUnsupportedZoomDialogIfNeededLocked(ActivityRecord r) {
- final Configuration globalConfig = getGlobalConfiguration();
- if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
- && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
- final Message msg = Message.obtain();
- msg.what = SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG;
- msg.obj = r;
- mUiHandler.sendMessage(msg);
- }
+ final AppWarnings getAppWarningsLocked() {
+ return mAppWarnings;
+ }
+
+ /**
+ * Shows app warning dialogs, if necessary.
+ *
+ * @param r activity record for which the warnings may be displayed
+ */
+ final void showAppWarningsIfNeededLocked(ActivityRecord r) {
+ mAppWarnings.showUnsupportedCompileSdkDialogIfNeeded(r);
+ mAppWarnings.showUnsupportedDisplaySizeDialogIfNeeded(r);
}
private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
String what, Object obj, ProcessRecord srcApp) {
app.lastActivityTime = now;
- if (app.activities.size() > 0) {
+ if (app.activities.size() > 0 || app.recentTasks.size() > 0) {
// Don't want to touch dependent processes that are hosting activities.
return index;
}
@@ -3380,7 +3402,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
ProcessRecord client) {
final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities
- || app.treatLikeActivity;
+ || app.treatLikeActivity || app.recentTasks.size() > 0;
final boolean hasService = false; // not impl yet. app.services.size() > 0;
if (!activityChange && hasActivity) {
// The process has activities, so we are only allowing activity-based adjustments
@@ -3484,7 +3506,8 @@ public class ActivityManagerService extends IActivityManager.Stub
int nextIndex;
if (hasActivity) {
final int N = mLruProcesses.size();
- if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) {
+ if ((app.activities.size() == 0 || app.recentTasks.size() > 0)
+ && mLruProcessActivityStart < (N - 1)) {
// Process doesn't have activities, but has clients with
// activities... move it up, but one below the top (the top
// should always have a real activity).
@@ -5116,7 +5139,8 @@ public class ActivityManagerService extends IActivityManager.Stub
// because we don't support returning them across task boundaries. Also, to
// keep backwards compatibility we remove the task from recents when finishing
// task with root activity.
- res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity);
+ res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
+ finishWithRootActivity, "finish-activity");
if (!res) {
Slog.i(TAG, "Removing task failed to finish activity");
}
@@ -5321,6 +5345,8 @@ public class ActivityManagerService extends IActivityManager.Stub
// Remove this application's activities from active lists.
boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);
+ app.clearRecentTasks();
+
app.activities.clear();
if (app.instr != null) {
@@ -8091,7 +8117,7 @@ public class ActivityManagerService extends IActivityManager.Stub
return false;
}
// An activity is consider to be in multi-window mode if its task isn't fullscreen.
- return !r.getTask().mFullscreen;
+ return r.inMultiWindowMode();
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -8600,6 +8626,16 @@ public class ActivityManagerService extends IActivityManager.Stub
}
switch (appop) {
case AppOpsManager.MODE_ALLOWED:
+ // If force-background-check is enabled, restrict all apps that aren't whitelisted.
+ if (mForceBackgroundCheck &&
+ UserHandle.isApp(uid) &&
+ !isOnDeviceIdleWhitelistLocked(uid)) {
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Force background check: " +
+ uid + "/" + packageName + " restricted");
+ }
+ return ActivityManager.APP_START_MODE_DELAYED;
+ }
return ActivityManager.APP_START_MODE_NORMAL;
case AppOpsManager.MODE_IGNORED:
return ActivityManager.APP_START_MODE_DELAYED;
@@ -8699,6 +8735,9 @@ public class ActivityManagerService extends IActivityManager.Stub
return ActivityManager.APP_START_MODE_NORMAL;
}
+ /**
+ * @return whether a UID is in the system, user or temp doze whitelist.
+ */
boolean isOnDeviceIdleWhitelistLocked(int uid) {
final int appId = UserHandle.getAppId(uid);
return Arrays.binarySearch(mDeviceIdleWhitelist, appId) >= 0
@@ -9044,6 +9083,19 @@ public class ActivityManagerService extends IActivityManager.Stub
this.targetUid = targetUid;
this.flags = flags;
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(NeededUriGrantsProto.TARGET_PACKAGE, targetPkg);
+ proto.write(NeededUriGrantsProto.TARGET_UID, targetUid);
+ proto.write(NeededUriGrantsProto.FLAGS, flags);
+
+ final int N = this.size();
+ for (int i=0; i<N; i++) {
+ this.get(i).writeToProto(proto, NeededUriGrantsProto.GRANTS);
+ }
+ proto.end(token);
+ }
}
/**
@@ -10097,8 +10149,8 @@ public class ActivityManagerService extends IActivityManager.Stub
} else {
// Task isn't in window manager yet since it isn't associated with a stack.
// Return the persist value from activity manager
- if (task.mBounds != null) {
- rect.set(task.mBounds);
+ if (!task.matchParentBounds()) {
+ rect.set(task.getBounds());
} else if (task.mLastNonFullscreenBounds != null) {
rect.set(task.mLastNonFullscreenBounds);
}
@@ -10279,7 +10331,8 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
try {
- return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS);
+ return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS,
+ "remove-task");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10469,12 +10522,12 @@ public class ActivityManagerService extends IActivityManager.Stub
+ " non-standard task " + taskId + " to windowing mode="
+ windowingMode);
}
- final ActivityDisplay display = task.getStack().getDisplay();
- final ActivityStack stack = display.getOrCreateStack(windowingMode,
- task.getStack().getActivityType(), toTop);
- // TODO: Use ActivityStack.setWindowingMode instead of re-parenting.
- task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
- "moveTaskToStack");
+
+ final ActivityStack stack = task.getStack();
+ if (toTop) {
+ stack.moveToFront("setTaskWindowingMode", task);
+ }
+ stack.setWindowingMode(windowingMode);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -11693,6 +11746,15 @@ public class ActivityManagerService extends IActivityManager.Stub
return true;
}
+ /**
+ * Returns the PackageManager. Used by classes hosted by {@link ActivityManagerService}. The
+ * PackageManager could be unavailable at construction time and therefore needs to be accessed
+ * on demand.
+ */
+ IPackageManager getPackageManager() {
+ return AppGlobals.getPackageManager();
+ }
+
PackageManagerInternal getPackageManagerInternalLocked() {
if (mPackageManagerInt == null) {
mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
@@ -12582,6 +12644,18 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ boolean checkAllowAppSwitchUid(int uid) {
+ ArrayMap<String, Integer> types = mAllowAppSwitchUids.get(UserHandle.getUserId(uid));
+ if (types != null) {
+ for (int i = types.size() - 1; i >= 0; i--) {
+ if (types.valueAt(i).intValue() == uid) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
boolean checkAppSwitchAllowedLocked(int sourcePid, int sourceUid,
int callingPid, int callingUid, String name) {
if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
@@ -12594,6 +12668,9 @@ public class ActivityManagerService extends IActivityManager.Stub
if (perm == PackageManager.PERMISSION_GRANTED) {
return true;
}
+ if (checkAllowAppSwitchUid(sourceUid)) {
+ return true;
+ }
// If the actual IPC caller is different from the logical source, then
// also see if they are allowed to control app switches.
@@ -12604,6 +12681,9 @@ public class ActivityManagerService extends IActivityManager.Stub
if (perm == PackageManager.PERMISSION_GRANTED) {
return true;
}
+ if (checkAllowAppSwitchUid(callingUid)) {
+ return true;
+ }
}
Slog.w(TAG, name + " request from " + sourceUid + " stopped");
@@ -14115,6 +14195,16 @@ public class ActivityManagerService extends IActivityManager.Stub
readGrantedUriPermissionsLocked();
}
+ final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+ if (pmi != null) {
+ pmi.registerLowPowerModeObserver(ServiceType.FORCE_BACKGROUND_CHECK,
+ state -> updateForceBackgroundCheck(state.batterySaverEnabled));
+ updateForceBackgroundCheck(
+ pmi.getLowPowerState(ServiceType.FORCE_BACKGROUND_CHECK).batterySaverEnabled);
+ } else {
+ Slog.wtf(TAG, "PowerManagerInternal not found.");
+ }
+
if (goingCallback != null) goingCallback.run();
traceLog.traceBegin("ActivityManagerStartApps");
mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
@@ -14213,6 +14303,23 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ private void updateForceBackgroundCheck(boolean enabled) {
+ synchronized (this) {
+ if (mForceBackgroundCheck != enabled) {
+ mForceBackgroundCheck = enabled;
+
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Force background check " + (enabled ? "enabled" : "disabled"));
+ }
+
+ if (mForceBackgroundCheck) {
+ // Stop background services for idle UIDs.
+ doStopUidForIdleUidsLocked();
+ }
+ }
+ }
+ }
+
void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
synchronized (this) {
mAppErrors.killAppAtUserRequestLocked(app, fromDialog);
@@ -14544,6 +14651,18 @@ public class ActivityManagerService extends IActivityManager.Stub
final String dropboxTag = processClass(process) + "_" + eventType;
if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
+ // Log to StatsLog before the rate-limiting.
+ // The logging below is adapated from appendDropboxProcessHeaders.
+ StatsLog.write(StatsLog.DROPBOX_ERROR_CHANGED,
+ process != null ? process.uid : -1,
+ dropboxTag,
+ processName,
+ process != null ? process.pid : -1,
+ (process != null && process.info != null) ?
+ (process.info.isInstantApp() ? 1 : 0) : -1,
+ activity != null ? activity.shortComponentName : null,
+ process != null ? (process.isInterestingToUserLocked() ? 1 : 0) : -1);
+
// Rate-limit how often we're willing to do the heavy lifting below to
// collect and record logs; currently 5 logs per 10 second period.
final long now = SystemClock.elapsedRealtime();
@@ -14868,6 +14987,7 @@ public class ActivityManagerService extends IActivityManager.Stub
boolean dumpVisibleStacksOnly = false;
boolean dumpFocusedStackOnly = false;
String dumpPackage = null;
+ int dumpAppId = -1;
int opti = 0;
while (opti < args.length) {
@@ -14922,6 +15042,25 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized (this) {
writeBroadcastsToProtoLocked(proto);
}
+ } else if ("provider".equals(cmd)) {
+ String[] newArgs;
+ String name;
+ if (opti >= args.length) {
+ name = null;
+ newArgs = EMPTY_STRING_ARRAY;
+ } else {
+ name = args[opti];
+ opti++;
+ newArgs = new String[args.length - opti];
+ if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
+ args.length - opti);
+ }
+ if (!dumpProviderProto(fd, pw, name, newArgs)) {
+ pw.println("No providers match: " + name);
+ pw.println("Use -h for help.");
+ }
+ } else if ("service".equals(cmd)) {
+ mServices.writeToProto(proto);
} else {
// default option, dump everything, output is ActivityManagerServiceProto
synchronized (this) {
@@ -14932,6 +15071,10 @@ public class ActivityManagerService extends IActivityManager.Stub
long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS);
writeBroadcastsToProtoLocked(proto);
proto.end(broadcastToken);
+
+ long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES);
+ mServices.writeToProto(proto);
+ proto.end(serviceToken);
}
}
proto.flush();
@@ -14939,6 +15082,16 @@ public class ActivityManagerService extends IActivityManager.Stub
return;
}
+ if (dumpPackage != null) {
+ try {
+ ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
+ dumpPackage, 0);
+ dumpAppId = UserHandle.getAppId(info.uid);
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
boolean more = false;
// Is the caller requesting to dump a particular piece of data?
if (opti < args.length) {
@@ -15046,7 +15199,7 @@ public class ActivityManagerService extends IActivityManager.Stub
args.length - opti);
}
synchronized (this) {
- dumpProcessesLocked(fd, pw, args, opti, true, dumpPackage);
+ dumpProcessesLocked(fd, pw, args, opti, true, dumpPackage, dumpAppId);
}
} else if ("oom".equals(cmd) || "o".equals(cmd)) {
synchronized (this) {
@@ -15232,7 +15385,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+ dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
}
} else {
@@ -15309,7 +15462,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+ dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
}
}
Binder.restoreCallingIdentity(origId);
@@ -15464,22 +15617,12 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- boolean dumpUids(PrintWriter pw, String dumpPackage, SparseArray<UidRecord> uids,
+ boolean dumpUids(PrintWriter pw, String dumpPackage, int dumpAppId, SparseArray<UidRecord> uids,
String header, boolean needSep) {
boolean printed = false;
- int whichAppId = -1;
- if (dumpPackage != null) {
- try {
- ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
- dumpPackage, 0);
- whichAppId = UserHandle.getAppId(info.uid);
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- }
- }
for (int i=0; i<uids.size(); i++) {
UidRecord uidRec = uids.valueAt(i);
- if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
+ if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != dumpAppId) {
continue;
}
if (!printed) {
@@ -15526,9 +15669,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpAll, String dumpPackage) {
+ int opti, boolean dumpAll, String dumpPackage, int dumpAppId) {
boolean needSep = false;
- boolean printedAnything = false;
int numPers = 0;
pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)");
@@ -15546,7 +15688,6 @@ public class ActivityManagerService extends IActivityManager.Stub
if (!needSep) {
pw.println(" All known processes:");
needSep = true;
- printedAnything = true;
}
pw.print(r.persistent ? " *PERS*" : " *APP*");
pw.print(" UID "); pw.print(procs.keyAt(ia));
@@ -15571,7 +15712,6 @@ public class ActivityManagerService extends IActivityManager.Stub
pw.println();
}
pw.println(" Isolated process list (sorted by uid):");
- printedAnything = true;
printed = true;
needSep = true;
}
@@ -15593,7 +15733,6 @@ public class ActivityManagerService extends IActivityManager.Stub
pw.println();
}
pw.println(" Active instrumentation:");
- printedAnything = true;
printed = true;
needSep = true;
}
@@ -15604,14 +15743,15 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (mActiveUids.size() > 0) {
- if (dumpUids(pw, dumpPackage, mActiveUids, "UID states:", needSep)) {
- printedAnything = needSep = true;
+ if (dumpUids(pw, dumpPackage, dumpAppId, mActiveUids, "UID states:", needSep)) {
+ needSep = true;
}
}
if (dumpAll) {
if (mValidateUids.size() > 0) {
- if (dumpUids(pw, dumpPackage, mValidateUids, "UID validation:", needSep)) {
- printedAnything = needSep = true;
+ if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:",
+ needSep)) {
+ needSep = true;
}
}
}
@@ -15628,7 +15768,6 @@ public class ActivityManagerService extends IActivityManager.Stub
pw.println("):");
dumpProcessOomList(pw, this, mLruProcesses, " ", "Proc", "PERS", false, dumpPackage);
needSep = true;
- printedAnything = true;
}
if (dumpAll || dumpPackage != null) {
@@ -15644,7 +15783,6 @@ public class ActivityManagerService extends IActivityManager.Stub
needSep = true;
pw.println(" PID mappings:");
printed = true;
- printedAnything = true;
}
pw.print(" PID #"); pw.print(mPidsSelfLocked.keyAt(i));
pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i));
@@ -15667,7 +15805,6 @@ public class ActivityManagerService extends IActivityManager.Stub
needSep = true;
pw.println(" Foreground Processes:");
printed = true;
- printedAnything = true;
}
pw.print(" PID #"); pw.print(mImportantProcesses.keyAt(i));
pw.print(": "); pw.println(mImportantProcesses.valueAt(i));
@@ -15678,7 +15815,6 @@ public class ActivityManagerService extends IActivityManager.Stub
if (mPersistentStartingProcesses.size() > 0) {
if (needSep) pw.println();
needSep = true;
- printedAnything = true;
pw.println(" Persisent processes that are starting:");
dumpProcessList(pw, this, mPersistentStartingProcesses, " ",
"Starting Norm", "Restarting PERS", dumpPackage);
@@ -15687,7 +15823,6 @@ public class ActivityManagerService extends IActivityManager.Stub
if (mRemovedProcesses.size() > 0) {
if (needSep) pw.println();
needSep = true;
- printedAnything = true;
pw.println(" Processes that are being removed:");
dumpProcessList(pw, this, mRemovedProcesses, " ",
"Removed Norm", "Removed PERS", dumpPackage);
@@ -15696,7 +15831,6 @@ public class ActivityManagerService extends IActivityManager.Stub
if (mProcessesOnHold.size() > 0) {
if (needSep) pw.println();
needSep = true;
- printedAnything = true;
pw.println(" Processes that are on old until the system is ready:");
dumpProcessList(pw, this, mProcessesOnHold, " ",
"OnHold Norm", "OnHold PERS", dumpPackage);
@@ -15705,9 +15839,6 @@ public class ActivityManagerService extends IActivityManager.Stub
needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage);
needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage);
- if (needSep) {
- printedAnything = true;
- }
if (dumpPackage == null) {
pw.println();
@@ -15914,6 +16045,32 @@ public class ActivityManagerService extends IActivityManager.Stub
pw.println(" mNativeDebuggingApp=" + mNativeDebuggingApp);
}
}
+ if (mAllowAppSwitchUids.size() > 0) {
+ boolean printed = false;
+ for (int i = 0; i < mAllowAppSwitchUids.size(); i++) {
+ ArrayMap<String, Integer> types = mAllowAppSwitchUids.valueAt(i);
+ for (int j = 0; j < types.size(); j++) {
+ if (dumpPackage == null ||
+ UserHandle.getAppId(types.valueAt(j).intValue()) == dumpAppId) {
+ if (needSep) {
+ pw.println();
+ needSep = false;
+ }
+ if (!printed) {
+ pw.println(" mAllowAppSwitchUids:");
+ printed = true;
+ }
+ pw.print(" User ");
+ pw.print(mAllowAppSwitchUids.keyAt(i));
+ pw.print(": Type ");
+ pw.print(types.keyAt(j));
+ pw.print(" = ");
+ UserHandle.formatUid(pw, types.valueAt(j).intValue());
+ pw.println();
+ }
+ }
+ }
+ }
if (dumpPackage == null) {
if (mAlwaysFinishActivities) {
pw.println(" mAlwaysFinishActivities=" + mAlwaysFinishActivities);
@@ -15953,10 +16110,7 @@ public class ActivityManagerService extends IActivityManager.Stub
pw.println();
}
}
-
- if (!printedAnything) {
- pw.println(" (nothing)");
- }
+ pw.println(" mForceBackgroundCheck=" + mForceBackgroundCheck);
}
boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args,
@@ -16063,6 +16217,15 @@ public class ActivityManagerService extends IActivityManager.Stub
return mProviderMap.dumpProvider(fd, pw, name, args, opti, dumpAll);
}
+ /**
+ * Similar to the dumpProvider, but only dumps the first matching provider.
+ * The provider is responsible for dumping as proto.
+ */
+ protected boolean dumpProviderProto(FileDescriptor fd, PrintWriter pw, String name,
+ String[] args) {
+ return mProviderMap.dumpProviderProto(fd, pw, name, args);
+ }
+
static class ItemMatcher {
ArrayList<ComponentName> components;
ArrayList<String> strings;
@@ -17883,7 +18046,7 @@ public class ActivityManagerService extends IActivityManager.Stub
PrintWriter catPw = new FastPrintWriter(catSw, false, 256);
String[] emptyArgs = new String[] { };
catPw.println();
- dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null);
+ dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null, -1);
catPw.println();
mServices.newServiceDumperLocked(null, catPw, emptyArgs, 0,
false, null).dumpLocked();
@@ -19296,13 +19459,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mRecentTasks.removeTasksByPackageName(ssp, userId);
mServices.forceStopPackageLocked(ssp, userId);
-
- // Hide the "unsupported display" dialog if necessary.
- if (mUnsupportedDisplaySizeDialog != null && ssp.equals(
- mUnsupportedDisplaySizeDialog.getPackageName())) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
- }
+ mAppWarnings.onPackageUninstalled(ssp);
mCompatModePackages.handlePackageUninstalledLocked(ssp);
mBatteryStatsService.notePackageUninstalled(ssp);
}
@@ -19381,13 +19538,8 @@ public class ActivityManagerService extends IActivityManager.Stub
Uri data = intent.getData();
String ssp;
if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
- // Hide the "unsupported display" dialog if necessary.
- if (mUnsupportedDisplaySizeDialog != null && ssp.equals(
- mUnsupportedDisplaySizeDialog.getPackageName())) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
- }
mCompatModePackages.handlePackageDataClearedLocked(ssp);
+ mAppWarnings.onPackageDataCleared(ssp);
}
break;
}
@@ -20447,9 +20599,11 @@ public class ActivityManagerService extends IActivityManager.Stub
if (app.thread != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
+ app.processName + " new config " + configCopy);
- app.thread.scheduleConfigurationChanged(configCopy);
+ mLifecycleManager.scheduleTransaction(app.thread,
+ new ConfigurationChangeItem(configCopy));
}
} catch (Exception e) {
+ Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e);
}
}
@@ -20577,8 +20731,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
if (isDensityChange && displayId == DEFAULT_DISPLAY) {
- // Reset the unsupported display size dialog.
- mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
+ mAppWarnings.onDensityChanged();
killAllBackgroundProcessesExcept(N,
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
@@ -20959,7 +21112,7 @@ public class ActivityManagerService extends IActivityManager.Stub
+ " instead of expected " + app);
if (r.app == null || (r.app.uid == app.uid)) {
// Only fix things up when they look sane
- r.app = app;
+ r.setProcess(app);
} else {
continue;
}
@@ -21038,6 +21191,11 @@ public class ActivityManagerService extends IActivityManager.Stub
adj += minLayer;
}
}
+ if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.recentTasks.size() > 0) {
+ procState = ActivityManager.PROCESS_STATE_CACHED_RECENT;
+ app.adjType = "cch-rec";
+ if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Raise to cached recent: " + app);
+ }
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
|| procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -22598,6 +22756,7 @@ public class ActivityManagerService extends IActivityManager.Stub
switch (app.curProcState) {
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
// This process is a cached process holding activities...
// assign it the next cached value for that type, and then
// step that cached level.
@@ -23212,6 +23371,24 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ /**
+ * Call {@link #doStopUidLocked} (which will also stop background services) for all idle UIDs.
+ */
+ void doStopUidForIdleUidsLocked() {
+ final int size = mActiveUids.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = mActiveUids.keyAt(i);
+ if (!UserHandle.isApp(uid)) {
+ continue;
+ }
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ if (!uidRec.idle) {
+ continue;
+ }
+ doStopUidLocked(uidRec.uid, uidRec);
+ }
+ }
+
final void doStopUidLocked(int uid, final UidRecord uidRec) {
mServices.stopInBackgroundLocked(uid);
enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
@@ -23322,7 +23499,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// has been removed.
for (i=mRemovedProcesses.size()-1; i>=0; i--) {
final ProcessRecord app = mRemovedProcesses.get(i);
- if (app.activities.size() == 0
+ if (app.activities.size() == 0 && app.recentTasks.size() == 0
&& app.curReceivers.isEmpty() && app.services.size() == 0) {
Slog.i(
TAG, "Exiting empty application process "
@@ -24189,6 +24366,32 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
}
+
+ @Override
+ public void setAllowAppSwitches(@NonNull String type, int uid, int userId) {
+ synchronized (ActivityManagerService.this) {
+ if (mUserController.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) {
+ ArrayMap<String, Integer> types = mAllowAppSwitchUids.get(userId);
+ if (types == null) {
+ if (uid < 0) {
+ return;
+ }
+ types = new ArrayMap<>();
+ mAllowAppSwitchUids.put(userId, types);
+ }
+ if (uid < 0) {
+ types.remove(type);
+ } else {
+ types.put(type, uid);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isRuntimeRestarted() {
+ return mSystemServiceManager.isRuntimeRestarted();
+ }
}
/**
diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java
index 9a16745c..a089e6ce 100644
--- a/com/android/server/am/ActivityRecord.java
+++ b/com/android/server/am/ActivityRecord.java
@@ -79,7 +79,6 @@ import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SAVED_STATE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES;
@@ -117,6 +116,7 @@ import static com.android.server.am.proto.ActivityRecordProto.IDENTIFIER;
import static com.android.server.am.proto.ActivityRecordProto.PROC_ID;
import static com.android.server.am.proto.ActivityRecordProto.STATE;
import static com.android.server.am.proto.ActivityRecordProto.VISIBLE;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
import static com.android.server.wm.proto.IdentifierProto.HASH_CODE;
import static com.android.server.wm.proto.IdentifierProto.TITLE;
import static com.android.server.wm.proto.IdentifierProto.USER_ID;
@@ -131,6 +131,12 @@ import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.ResultInfo;
+import android.app.servertransaction.MoveToDisplayItem;
+import android.app.servertransaction.MultiWindowModeChangeItem;
+import android.app.servertransaction.NewIntentItem;
+import android.app.servertransaction.PipModeChangeItem;
+import android.app.servertransaction.WindowVisibilityItem;
+import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -165,7 +171,6 @@ import android.view.IApplicationToken;
import android.view.WindowManager.LayoutParams;
import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.ReferrerIntent;
import com.android.internal.util.XmlUtils;
@@ -343,12 +348,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
// on the window.
int mRotationAnimationHint = -1;
- // The bounds of this activity. Mainly used for aspect-ratio compatibility.
- // TODO(b/36505427): Every level on ConfigurationContainer now has bounds information, which
- // directly affects the configuration. We should probably move this into that class and have it
- // handle calculating override configuration from the bounds.
- private final Rect mBounds = new Rect();
-
private boolean mShowWhenLocked;
private boolean mTurnScreenOn;
@@ -414,8 +413,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
if (!getOverrideConfiguration().equals(EMPTY)) {
pw.println(prefix + "OverrideConfiguration=" + getOverrideConfiguration());
}
- if (!mBounds.isEmpty()) {
- pw.println(prefix + "mBounds=" + mBounds);
+ if (!matchParentBounds()) {
+ pw.println(prefix + "bounds=" + getBounds());
}
if (resultTo != null || resultWho != null) {
pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
@@ -618,8 +617,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
"Reporting activity moved to display" + ", activityRecord=" + this
+ ", displayId=" + displayId + ", config=" + config);
- app.thread.scheduleActivityMovedToDisplay(appToken, displayId,
- new Configuration(config));
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new MoveToDisplayItem(displayId, config));
} catch (RemoteException e) {
// If process died, whatever.
}
@@ -636,7 +635,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + ", config: "
+ config);
- app.thread.scheduleActivityConfigurationChanged(appToken, new Configuration(config));
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new ActivityConfigurationChangeItem(config));
} catch (RemoteException e) {
// If process died, whatever.
}
@@ -647,8 +647,13 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
return;
}
+ if (task.getStack().deferScheduleMultiWindowModeChanged()) {
+ // Don't do anything if we are currently deferring multi-window mode change.
+ return;
+ }
+
// An activity is considered to be in multi-window mode if its task isn't fullscreen.
- final boolean inMultiWindowMode = !task.mFullscreen;
+ final boolean inMultiWindowMode = inMultiWindowMode();
if (inMultiWindowMode != mLastReportedMultiWindowMode) {
mLastReportedMultiWindowMode = inMultiWindowMode;
scheduleMultiWindowModeChanged(getConfiguration());
@@ -657,8 +662,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
private void scheduleMultiWindowModeChanged(Configuration overrideConfig) {
try {
- app.thread.scheduleMultiWindowModeChanged(appToken, mLastReportedMultiWindowMode,
- overrideConfig);
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new MultiWindowModeChangeItem(mLastReportedMultiWindowMode,
+ overrideConfig));
} catch (Exception e) {
// If process died, I don't care.
}
@@ -674,7 +680,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
// Picture-in-picture mode changes also trigger a multi-window mode change as well, so
// update that here in order
mLastReportedPictureInPictureMode = inPictureInPictureMode;
- mLastReportedMultiWindowMode = inPictureInPictureMode;
+ mLastReportedMultiWindowMode = inMultiWindowMode();
final Configuration newConfig = task.computeNewOverrideConfigurationForBounds(
targetStackBounds, null);
schedulePictureInPictureModeChanged(newConfig);
@@ -684,8 +690,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
private void schedulePictureInPictureModeChanged(Configuration overrideConfig) {
try {
- app.thread.schedulePictureInPictureModeChanged(appToken,
- mLastReportedPictureInPictureMode, overrideConfig);
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new PipModeChangeItem(mLastReportedPictureInPictureMode,
+ overrideConfig));
} catch (Exception e) {
// If process died, no one cares.
}
@@ -934,6 +941,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
}
}
+ void setProcess(ProcessRecord proc) {
+ app = proc;
+ final ActivityRecord root = task != null ? task.getRootActivity() : null;
+ if (root == this) {
+ task.setRootProcess(proc);
+ }
+ }
+
AppWindowContainerController getWindowContainerController() {
return mWindowContainerController;
}
@@ -958,14 +973,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
(info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges,
task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(),
appInfo.targetSdkVersion, mRotationAnimationHint,
- ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L, mBounds);
+ ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L);
task.addActivityToTop(this);
// When an activity is started directly into a split-screen fullscreen stack, we need to
// update the initial multi-window modes so that the callbacks are scheduled correctly when
// the user leaves that mode.
- mLastReportedMultiWindowMode = !task.mFullscreen;
+ mLastReportedMultiWindowMode = inMultiWindowMode();
mLastReportedPictureInPictureMode = inPinnedWindowingMode();
}
@@ -1364,8 +1379,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
try {
ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
ar.add(rintent);
- app.thread.scheduleNewIntent(
- ar, appToken, state == PAUSED /* andPause */);
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new NewIntentItem(ar, state == PAUSED));
unsent = false;
} catch (RemoteException e) {
Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
@@ -1587,7 +1602,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
setVisible(true);
sleeping = false;
app.pendingUiClean = true;
- app.thread.scheduleWindowVisibility(appToken, true /* showWindow */);
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new WindowVisibilityItem(true /* showWindow */));
// The activity may be waiting for stop, but that is no longer appropriate for it.
mStackSupervisor.mStoppingActivities.remove(this);
mStackSupervisor.mGoingToSleepActivities.remove(this);
@@ -2164,33 +2180,25 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
mLastReportedConfiguration.setConfiguration(global, override);
}
- @Override
- public void onOverrideConfigurationChanged(Configuration newConfig) {
- final Configuration currentConfig = getOverrideConfiguration();
- if (currentConfig.equals(newConfig)) {
- return;
- }
- super.onOverrideConfigurationChanged(newConfig);
- if (mWindowContainerController == null) {
- return;
- }
- mWindowContainerController.onOverrideConfigurationChanged(newConfig, mBounds);
- }
-
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private void updateOverrideConfiguration() {
mTmpConfig.unset();
computeBounds(mTmpBounds);
- if (mTmpBounds.equals(mBounds)) {
+
+ if (mTmpBounds.equals(getOverrideBounds())) {
return;
}
- mBounds.set(mTmpBounds);
+ setBounds(mTmpBounds);
+
+ final Rect updatedBounds = getOverrideBounds();
+
// Bounds changed...update configuration to match.
- if (!mBounds.isEmpty()) {
- task.computeOverrideConfiguration(mTmpConfig, mBounds, null /* insetBounds */,
+ if (!matchParentBounds()) {
+ task.computeOverrideConfiguration(mTmpConfig, updatedBounds, null /* insetBounds */,
false /* overrideWidth */, false /* overrideHeight */);
}
+
onOverrideConfigurationChanged(mTmpConfig);
}
@@ -2217,7 +2225,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
outBounds.setEmpty();
final float maxAspectRatio = info.maxAspectRatio;
final ActivityStack stack = getStack();
- if (task == null || stack == null || !task.mFullscreen || maxAspectRatio == 0
+ if (task == null || stack == null || task.inMultiWindowMode() || maxAspectRatio == 0
|| isInVrUiMode(getConfiguration())) {
// We don't set override configuration if that activity task isn't fullscreen. I.e. the
// activity is in multi-window mode. Or, there isn't a max aspect ratio specified for
@@ -2248,11 +2256,11 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
if (containingAppWidth <= maxActivityWidth && containingAppHeight <= maxActivityHeight) {
// The display matches or is less than the activity aspect ratio, so nothing else to do.
// Return the existing bounds. If this method is running for the first time,
- // {@link mBounds} will be empty (representing no override). If the method has run
- // before, then effect of {@link mBounds} will already have been applied to the
+ // {@link #getOverrideBounds()} will be empty (representing no override). If the method has run
+ // before, then effect of {@link #getOverrideBounds()} will already have been applied to the
// value returned from {@link getConfiguration}. Refer to
// {@link TaskRecord#computeOverrideConfiguration}.
- outBounds.set(mBounds);
+ outBounds.set(getOverrideBounds());
return;
}
@@ -2264,12 +2272,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
outBounds.offsetTo(left, 0 /* top */);
}
- /** Get bounds of the activity. */
- @VisibleForTesting
- Rect getBounds() {
- return new Rect(mBounds);
- }
-
/**
* Make sure the given activity matches the current configuration. Returns false if the activity
* had to be destroyed. Returns true if the configuration is the same, or the activity will
@@ -2549,7 +2551,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
}
results = null;
newIntents = null;
- service.showUnsupportedZoomDialogIfNeededLocked(this);
+ service.getAppWarningsLocked().onResumeActivity(this);
service.showAskCompatModeDialogLocked(this);
} else {
service.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this);
diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java
index c086c529..bdfd82f4 100644
--- a/com/android/server/am/ActivityStack.java
+++ b/com/android/server/am/ActivityStack.java
@@ -104,6 +104,13 @@ import android.app.IActivityController;
import android.app.ResultInfo;
import android.app.WindowConfiguration.ActivityType;
import android.app.WindowConfiguration.WindowingMode;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.NewIntentItem;
+import android.app.servertransaction.WindowVisibilityItem;
+import android.app.servertransaction.DestroyActivityItem;
+import android.app.servertransaction.PauseActivityItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.StopActivityItem;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -156,7 +163,6 @@ import java.util.Set;
*/
class ActivityStack<T extends StackWindowController> extends ConfigurationContainer
implements StackWindowListener {
-
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_AM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_APP = TAG + POSTFIX_APP;
@@ -322,11 +328,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
*/
boolean mForceHidden = false;
- // Whether or not this stack covers the entire screen; by default stacks are fullscreen
- boolean mFullscreen = true;
- // Current bounds of the stack or null if fullscreen.
- Rect mBounds = null;
-
private boolean mUpdateBoundsDeferred;
private boolean mUpdateBoundsDeferredCalled;
private final Rect mDeferredBounds = new Rect();
@@ -342,8 +343,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
/** The attached Display's unique identifier, or -1 if detached */
int mDisplayId;
- /** Temp variables used during override configuration update. */
- private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>();
private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
private final Rect mTmpRect2 = new Rect();
@@ -495,16 +494,18 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// Need to make sure windowing mode is supported.
int windowingMode = display.resolveWindowingMode(
- null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());;
+ null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());
if (splitScreenStack == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
// Resolution to split-screen secondary for the primary split-screen stack means we want
// to go fullscreen.
windowingMode = WINDOWING_MODE_FULLSCREEN;
}
+ final boolean alreadyInSplitScreenMode = display.hasSplitScreenPrimaryStack();
+
// Take any required action due to us not supporting the preferred windowing mode.
if (windowingMode != preferredWindowingMode && isActivityTypeStandardOrUndefined()) {
- if (display.hasSplitScreenPrimaryStack()
+ if (alreadyInSplitScreenMode
&& (preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
|| preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
// Looks like we can't launch in split screen mode, go ahead an dismiss split-screen
@@ -574,11 +575,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
}
- if (!Objects.equals(mBounds, mTmpRect2)) {
+ if (!Objects.equals(getOverrideBounds(), mTmpRect2)) {
resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
}
} finally {
- if (mDisplayId == DEFAULT_DISPLAY
+ if (!alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY
&& windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
// Make sure recents stack exist when creating a dock stack as it normally needs to
// be on the other side of the docked stack and we make visibility decisions based
@@ -641,9 +642,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
*/
private void postAddToDisplay(ActivityDisplay activityDisplay, Rect bounds, boolean onTop) {
mDisplayId = activityDisplay.mDisplayId;
- mBounds = bounds != null ? new Rect(bounds) : null;
- mFullscreen = mBounds == null;
-
+ setBounds(bounds);
onParentChanged();
activityDisplay.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM);
@@ -651,7 +650,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// If we created a docked stack we want to resize it so it resizes all other stacks
// in the system.
mStackSupervisor.resizeDockedStackLocked(
- mBounds, null, null, null, null, PRESERVE_WINDOWS);
+ getOverrideBounds(), null, null, null, null, PRESERVE_WINDOWS);
}
}
@@ -766,8 +765,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return false;
}
- void setBounds(Rect bounds) {
- mBounds = mFullscreen ? null : new Rect(bounds);
+ @Override
+ public int setBounds(Rect bounds) {
+ return super.setBounds(!inMultiWindowMode() ? null : bounds);
}
ActivityRecord topRunningActivityLocked() {
@@ -1428,8 +1428,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
prev.userId, System.identityHashCode(prev),
prev.shortComponentName);
mService.updateUsageStats(prev, false);
- prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
- userLeaving, prev.configChangeFlags, pauseImmediately);
+
+ mService.mLifecycleManager.scheduleTransaction(prev.app.thread, prev.appToken,
+ new PauseActivityItem(prev.finishing, userLeaving,
+ prev.configChangeFlags, pauseImmediately));
} catch (Exception e) {
// Ignore exception, if process died other code will cleanup.
Slog.w(TAG, "Exception thrown during pause", e);
@@ -1678,12 +1680,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return true;
}
- /** Returns true if the stack is currently considered visible. */
- boolean isVisible() {
- return mWindowContainerController != null && mWindowContainerController.isVisible()
- && !mForceHidden;
- }
-
boolean isTopStackOnDisplay() {
return getDisplay().isTopStack(this);
}
@@ -2064,7 +2060,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
if (r.app != null && r.app.thread != null) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
"Scheduling invisibility: " + r);
- r.app.thread.scheduleWindowVisibility(r.appToken, false);
+ mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+ new WindowVisibilityItem(false /* showWindow */));
}
// Reset the flag indicating that an app can enter picture-in-picture once the
@@ -2495,7 +2492,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// apps, maybeUpdateTransitToWallpaper() will fail to identify this as a
// TRANSIT_WALLPAPER_OPEN animation, and run some funny animation.
final boolean lastActivityTranslucent = lastStack != null
- && (!lastStack.mFullscreen
+ && (lastStack.inMultiWindowMode()
|| (lastStack.mLastPausedActivity != null
&& !lastStack.mLastPausedActivity.fullscreen));
@@ -2584,13 +2581,15 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
if (!next.finishing && N > 0) {
if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
"Delivering results to " + next + ": " + a);
- next.app.thread.scheduleSendResult(next.appToken, a);
+ mService.mLifecycleManager.scheduleTransaction(next.app.thread,
+ next.appToken, new ActivityResultItem(a));
}
}
if (next.newIntents != null) {
- next.app.thread.scheduleNewIntent(
- next.newIntents, next.appToken, false /* andPause */);
+ mService.mLifecycleManager.scheduleTransaction(next.app.thread,
+ next.appToken, new NewIntentItem(next.newIntents,
+ false /* andPause */));
}
// Well the app will no longer be stopped.
@@ -2602,13 +2601,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
next.shortComponentName);
next.sleeping = false;
- mService.showUnsupportedZoomDialogIfNeededLocked(next);
+ mService.getAppWarningsLocked().onResumeActivity(next);
mService.showAskCompatModeDialogLocked(next);
next.app.pendingUiClean = true;
next.app.forceProcessStateUpTo(mService.mTopProcessState);
next.clearOptionsLocked();
- next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
- mService.isNextTransitionForward(), resumeAnimOptions);
+ mService.mLifecycleManager.scheduleTransaction(next.app.thread, next.appToken,
+ new ResumeActivityItem(next.app.repProcState,
+ mService.isNextTransitionForward()));
if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
+ next);
@@ -2739,8 +2739,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
position = getAdjustedPositionForTask(task, position, null /* starting */);
mTaskHistory.remove(task);
mTaskHistory.add(position, task);
- mWindowContainerController.positionChildAt(task.getWindowContainerController(), position,
- task.mBounds, task.getOverrideConfiguration());
+ mWindowContainerController.positionChildAt(task.getWindowContainerController(), position);
updateTaskMovement(task, true);
}
@@ -3259,7 +3258,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
list.add(new ResultInfo(resultWho, requestCode,
resultCode, data));
- r.app.thread.scheduleSendResult(r.appToken, list);
+ mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+ new ActivityResultItem(list));
return;
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending result to " + r, e);
@@ -3387,7 +3387,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
EventLogTags.writeAmStopActivity(
r.userId, System.identityHashCode(r), r.shortComponentName);
- r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags);
+ mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+ new StopActivityItem(r.visible, r.configChangeFlags));
if (shouldSleepOrShutDownActivities()) {
r.setSleeping(true);
}
@@ -4019,7 +4020,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// TODO: If the callers to removeTask() changes such that we have multiple places
// where we are destroying the task, move this back into removeTask()
mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */,
- !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY);
+ !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason);
}
// We must keep the task around until all activities are destroyed. The following
@@ -4184,8 +4185,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
try {
if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r);
- r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,
- r.configChangeFlags);
+ mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+ new DestroyActivityItem(r.finishing, r.configChangeFlags));
} catch (Exception e) {
// We can just ignore exceptions here... if the process
// has crashed, our death notification will clean things
@@ -4602,8 +4603,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// TODO: Can only be called from special methods in ActivityStackSupervisor.
// Need to consolidate those calls points into this resize method so anyone can call directly.
void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) {
- bounds = TaskRecord.validateBounds(bounds);
-
if (!updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) {
return;
}
@@ -4613,7 +4612,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds;
mTmpBounds.clear();
- mTmpConfigs.clear();
mTmpInsetBounds.clear();
for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
@@ -4624,7 +4622,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// 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.
- mTmpRect2.set(task.mBounds);
+ mTmpRect2.set(task.getOverrideBounds());
fitWithinBounds(mTmpRect2, bounds);
task.updateOverrideConfiguration(mTmpRect2);
} else {
@@ -4632,15 +4630,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
}
- mTmpConfigs.put(task.taskId, task.getOverrideConfiguration());
- mTmpBounds.put(task.taskId, task.mBounds);
+ mTmpBounds.put(task.taskId, task.getOverrideBounds());
if (tempTaskInsetBounds != null) {
mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds);
}
}
- mFullscreen = mWindowContainerController.resize(bounds, mTmpConfigs, mTmpBounds,
- mTmpInsetBounds);
+ mWindowContainerController.resize(bounds, mTmpBounds, mTmpInsetBounds);
setBounds(bounds);
}
@@ -4655,7 +4651,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
* @param stackBounds Bounds within which the other bounds should remain.
*/
private static void fitWithinBounds(Rect bounds, Rect stackBounds) {
- if (stackBounds == null || stackBounds.contains(bounds)) {
+ if (stackBounds == null || stackBounds.isEmpty() || stackBounds.contains(bounds)) {
return;
}
@@ -4873,8 +4869,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
pw.println("");
}
pw.println(prefix + "Task id #" + task.taskId);
- pw.println(prefix + "mFullscreen=" + task.mFullscreen);
- pw.println(prefix + "mBounds=" + task.mBounds);
+ pw.println(prefix + "mBounds=" + task.getOverrideBounds());
pw.println(prefix + "mMinWidth=" + task.mMinWidth);
pw.println(prefix + "mMinHeight=" + task.mMinHeight);
pw.println(prefix + "mLastNonFullscreenBounds=" + task.mLastNonFullscreenBounds);
@@ -4981,7 +4976,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
if (isOnHomeDisplay() && mode != REMOVE_TASK_MODE_MOVING_TO_TOP
&& mStackSupervisor.isFocusedStack(this)) {
String myReason = reason + " leftTaskHistoryEmpty";
- if (mFullscreen || !adjustFocusToNextFocusableStack(myReason)) {
+ if (!inMultiWindowMode() || !adjustFocusToNextFocusableStack(myReason)) {
mStackSupervisor.moveHomeStackToFront(myReason);
}
}
@@ -5004,15 +4999,24 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
boolean toTop) {
+ return createTaskRecord(taskId, info, intent, voiceSession, voiceInteractor, toTop,
+ null /*activity*/, null /*source*/, null /*options*/);
+ }
+
+ TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ boolean toTop, ActivityRecord activity, ActivityRecord source,
+ ActivityOptions options) {
final TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession,
voiceInteractor);
// add the task to stack first, mTaskPositioner might need the stack association
addTask(task, toTop, "createTaskRecord");
final boolean isLockscreenShown = mService.mStackSupervisor.getKeyguardController()
.isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
- if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout)
- && mBounds != null && task.isResizeable() && !isLockscreenShown) {
- task.updateOverrideConfiguration(mBounds);
+ if (!mStackSupervisor.getLaunchingBoundsController()
+ .layoutTask(task, info.windowLayout, activity, source, options)
+ && !matchParentBounds() && task.isResizeable() && !isLockscreenShown) {
+ task.updateOverrideConfiguration(getOverrideBounds());
}
task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
return task;
@@ -5174,10 +5178,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
}
proto.write(DISPLAY_ID, mDisplayId);
- if (mBounds != null) {
- mBounds.writeToProto(proto, BOUNDS);
+ if (!matchParentBounds()) {
+ final Rect bounds = getOverrideBounds();
+ bounds.writeToProto(proto, BOUNDS);
}
- proto.write(FULLSCREEN, mFullscreen);
+
+ // TODO: Remove, no longer needed with windowingMode.
+ proto.write(FULLSCREEN, matchParentBounds());
proto.end(token);
}
}
diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java
index 745e9fbc..445bf679 100644
--- a/com/android/server/am/ActivityStackSupervisor.java
+++ b/com/android/server/am/ActivityStackSupervisor.java
@@ -114,6 +114,7 @@ import android.app.ResultInfo;
import android.app.WaitResult;
import android.app.WindowConfiguration.ActivityType;
import android.app.WindowConfiguration.WindowingMode;
+import android.app.servertransaction.LaunchActivityItem;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -1270,7 +1271,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// schedule launch ticks to collect information about slow apps.
r.startLaunchTickingLocked();
- r.app = app;
+ r.setProcess(app);
if (mKeyguardController.isKeyguardLocked()) {
r.notifyUnknownVisibilityLaunched();
@@ -1358,7 +1359,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY);
r.sleeping = false;
r.forceNewConfig = false;
- mService.showUnsupportedZoomDialogIfNeededLocked(r);
+ mService.getAppWarningsLocked().onStartActivity(r);
mService.showAskCompatModeDialogLocked(r);
r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo);
ProfilerInfo profilerInfo = null;
@@ -1392,7 +1393,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
r.setLastReportedConfiguration(mergedConfiguration);
logIfTransactionTooLarge(r.intent, r.icicle);
- app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
+ mService.mLifecycleManager.scheduleTransaction(app.thread, r.appToken,
+ new LaunchActivityItem(new Intent(r.intent),
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global
// and override configs.
@@ -1400,7 +1402,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
r.persistentState, results, newIntents, !andResume,
- mService.isNextTransitionForward(), profilerInfo);
+ mService.isNextTransitionForward(), profilerInfo));
if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
// This may be a heavy-weight process! Note that the package
@@ -2118,7 +2120,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
- final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+ final Rect bounds = options.getLaunchBounds();
task.updateOverrideConfiguration(bounds);
ActivityStack stack = getLaunchStack(null, options, task, ON_TOP);
@@ -2626,7 +2628,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// TODO: Checking for isAttached might not be needed as if the user passes in null
// dockedBounds then they want the docked stack to be dismissed.
- if (stack.mFullscreen || (dockedBounds == null && !stack.isAttached())) {
+ if (stack.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ || (dockedBounds == null && !stack.isAttached())) {
// The dock stack either was dismissed or went fullscreen, which is kinda the same.
// In this case we make all other static stacks fullscreen and move all
// docked stack tasks to the fullscreen stack.
@@ -2736,7 +2739,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
} else {
for (int i = tasks.size() - 1; i >= 0; i--) {
removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */,
- REMOVE_FROM_RECENTS);
+ REMOVE_FROM_RECENTS, "remove-stack");
}
}
}
@@ -2769,8 +2772,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
/**
* See {@link #removeTaskByIdLocked(int, boolean, boolean, boolean)}
*/
- boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) {
- return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY);
+ boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
+ String reason) {
+ return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY,
+ reason);
}
/**
@@ -2784,10 +2789,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
* @return Returns true if the given task was found and removed.
*/
boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
- boolean pauseImmediately) {
+ boolean pauseImmediately, String reason) {
final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (tr != null) {
- tr.removeTaskActivitiesLocked(pauseImmediately);
+ tr.removeTaskActivitiesLocked(pauseImmediately, reason);
cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
mService.mLockTaskController.clearLockedTask(tr);
if (tr.isPersistable) {
@@ -2921,7 +2926,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
@Override
public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
- // TODO: Trim active task once b/68045330 is fixed
+ 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 */, !PAUSE_IMMEDIATELY, "recent-task-trimmed");
+ }
task.removedFromRecents();
}
@@ -3058,7 +3068,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(stack, task.mBounds, null /* tempTaskBounds */,
+ resizeStackLocked(stack, task.getOverrideBounds(), null /* tempTaskBounds */,
null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
true /* allowResizeInDockedMode */, !DEFER_RESUME);
@@ -3782,9 +3792,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
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);
+ pw.println(" mBounds=" + stack.getOverrideBounds());
printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage,
needSep);
@@ -4054,6 +4063,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
private void handleDisplayChanged(int displayId) {
synchronized (mService) {
ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+ // TODO: The following code block should be moved into {@link ActivityDisplay}.
if (activityDisplay != null) {
// The window policy is responsible for stopping activities on the default display
if (displayId != Display.DEFAULT_DISPLAY) {
@@ -4067,7 +4077,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
activityDisplay.mOffToken = null;
}
}
- // TODO: Update the bounds.
+
+ activityDisplay.updateBounds();
}
mWindowManager.onDisplayChanged(displayId);
}
@@ -4289,7 +4300,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return;
}
- scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds);
+ scheduleUpdatePictureInPictureModeIfNeeded(task, stack.getOverrideBounds());
}
void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) {
@@ -4297,6 +4308,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
final ActivityRecord r = task.mActivities.get(i);
if (r.app != null && r.app.thread != null) {
mPipModeChangedActivities.add(r);
+ // If we are scheduling pip change, then remove this activity from multi-window
+ // change list as the processing of pip change will make sure multi-window changed
+ // message is processed in the right order relative to pip changed.
+ mMultiWindowModeChangedActivities.remove(r);
}
}
mPipModeChangedTargetStackBounds = targetStackBounds;
diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java
index 9b8cbc10..2fc5dda6 100644
--- a/com/android/server/am/ActivityStarter.java
+++ b/com/android/server/am/ActivityStarter.java
@@ -134,7 +134,6 @@ class ActivityStarter {
private static final int INVALID_LAUNCH_MODE = -1;
private final ActivityManagerService mService;
- private final IPackageManager mPackageManager;
private final ActivityStackSupervisor mSupervisor;
private final ActivityStartInterceptor mInterceptor;
@@ -234,9 +233,8 @@ class ActivityStarter {
mIntentDelivered = false;
}
- ActivityStarter(ActivityManagerService service, IPackageManager packageManager) {
+ ActivityStarter(ActivityManagerService service) {
mService = service;
- mPackageManager = packageManager;
mSupervisor = mService.mStackSupervisor;
mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
}
@@ -379,7 +377,7 @@ class ActivityStarter {
&& sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
try {
intent.addCategory(Intent.CATEGORY_VOICE);
- if (!mPackageManager.activitySupportsIntent(
+ if (!mService.getPackageManager().activitySupportsIntent(
intent.getComponent(), intent, resolvedType)) {
Slog.w(TAG,
"Activity being started in current voice task does not support voice: "
@@ -397,7 +395,7 @@ class ActivityStarter {
// If the caller is starting a new voice session, just make sure the target
// is actually allowing it to run this way.
try {
- if (!mPackageManager.activitySupportsIntent(intent.getComponent(),
+ if (!mService.getPackageManager().activitySupportsIntent(intent.getComponent(),
intent, resolvedType)) {
Slog.w(TAG,
"Activity being started in new voice task does not support: "
@@ -608,21 +606,9 @@ class ActivityStarter {
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.");
- 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);
+ final int clearTaskFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK;
+ boolean clearedTask = (mLaunchFlags & clearTaskFlags) == clearTaskFlags
+ && mReuseTask != null;
if (startedActivityStack.inPinnedWindowingMode()
&& (result == START_TASK_TO_FRONT || result == START_DELIVERED_TO_TOP
|| clearedTask)) {
@@ -1758,7 +1744,8 @@ class ActivityStarter {
mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
- mVoiceInteractor, !mLaunchTaskBehind /* toTop */);
+ mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity, mSourceRecord,
+ mOptions);
addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
updateBounds(mStartActivity.getTask(), mLaunchBounds);
@@ -1967,7 +1954,7 @@ class ActivityStarter {
final ActivityRecord prev = mTargetStack.getTopActivity();
final TaskRecord task = (prev != null) ? prev.getTask() : mTargetStack.createTaskRecord(
mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info,
- mIntent, null, null, true);
+ mIntent, null, null, true, mStartActivity, mSourceRecord, mOptions);
addOrReparentStartingActivity(task, "setTaskToCurrentTopOrCreateNewTask");
mTargetStack.positionChildWindowContainerAtTop(task);
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
@@ -2124,18 +2111,13 @@ class ActivityStarter {
return mReuseTask.getStack();
}
- final int vrDisplayId = mPreferredDisplayId == mService.mVr2dDisplayId
- ? mPreferredDisplayId : INVALID_DISPLAY;
- final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
- vrDisplayId);
-
- if (launchStack != null) {
- return launchStack;
- }
-
if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
|| mPreferredDisplayId != DEFAULT_DISPLAY) {
- return null;
+ // We don't pass in the default display id into the get launch stack call so it can do a
+ // full resolution.
+ final int candidateDisplay =
+ mPreferredDisplayId != DEFAULT_DISPLAY ? mPreferredDisplayId : INVALID_DISPLAY;
+ return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP, candidateDisplay);
}
// Otherwise handle adjacent launch.
@@ -2167,7 +2149,7 @@ class ActivityStarter {
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;
+ return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP);
} else {
return dockedStack;
}
diff --git a/com/android/server/am/AppBindRecord.java b/com/android/server/am/AppBindRecord.java
index df833ad8..7b385978 100644
--- a/com/android/server/am/AppBindRecord.java
+++ b/com/android/server/am/AppBindRecord.java
@@ -17,6 +17,9 @@
package com.android.server.am;
import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.AppBindRecordProto;
import java.io.PrintWriter;
@@ -60,4 +63,18 @@ final class AppBindRecord {
+ Integer.toHexString(System.identityHashCode(this))
+ " " + service.shortName + ":" + client.processName + "}";
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(AppBindRecordProto.HEX_HASH,
+ Integer.toHexString(System.identityHashCode(this)));
+ if (client != null) {
+ client.writeToProto(proto, AppBindRecordProto.CLIENT);
+ }
+ final int N = connections.size();
+ for (int i=0; i<N; i++) {
+ connections.valueAt(i).writeToProto(proto, AppBindRecordProto.CONNECTIONS);
+ }
+ proto.end(token);
+ }
}
diff --git a/com/android/server/am/AppTaskImpl.java b/com/android/server/am/AppTaskImpl.java
index 17626ea1..ab86dbdb 100644
--- a/com/android/server/am/AppTaskImpl.java
+++ b/com/android/server/am/AppTaskImpl.java
@@ -61,7 +61,7 @@ class AppTaskImpl extends IAppTask.Stub {
try {
// We remove the task from recents to preserve backwards
if (!mService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
- REMOVE_FROM_RECENTS)) {
+ REMOVE_FROM_RECENTS, "finish-and-remove-task")) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
} finally {
diff --git a/com/android/server/am/AppWarnings.java b/com/android/server/am/AppWarnings.java
new file mode 100644
index 00000000..a3c03450
--- /dev/null
+++ b/com/android/server/am/AppWarnings.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 android.annotation.UiThread;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.AtomicFile;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages warning dialogs shown during application lifecycle.
+ */
+class AppWarnings {
+ private static final String TAG = "AppWarnings";
+ private static final String CONFIG_FILE_NAME = "packages-warnings.xml";
+
+ public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01;
+ public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
+
+ private final HashMap<String, Integer> mPackageFlags = new HashMap<>();
+
+ private final ActivityManagerService mAms;
+ private final Context mUiContext;
+ private final ConfigHandler mAmsHandler;
+ private final UiHandler mUiHandler;
+ private final AtomicFile mConfigFile;
+
+ private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
+ private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog;
+
+ /**
+ * Creates a new warning dialog manager.
+ * <p>
+ * <strong>Note:</strong> Must be called from the ActivityManagerService thread.
+ *
+ * @param ams
+ * @param uiContext
+ * @param amsHandler
+ * @param uiHandler
+ * @param systemDir
+ */
+ public AppWarnings(ActivityManagerService ams, Context uiContext, Handler amsHandler,
+ Handler uiHandler, File systemDir) {
+ mAms = ams;
+ mUiContext = uiContext;
+ mAmsHandler = new ConfigHandler(amsHandler.getLooper());
+ mUiHandler = new UiHandler(uiHandler.getLooper());
+ mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME));
+
+ readConfigFromFileAmsThread();
+ }
+
+ /**
+ * Shows the "unsupported display size" warning, if necessary.
+ *
+ * @param r activity record for which the warning may be displayed
+ */
+ public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) {
+ final Configuration globalConfig = mAms.getGlobalConfiguration();
+ if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
+ && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
+ mUiHandler.showUnsupportedDisplaySizeDialog(r);
+ }
+ }
+
+ /**
+ * Shows the "unsupported compile SDK" warning, if necessary.
+ *
+ * @param r activity record for which the warning may be displayed
+ */
+ public void showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r) {
+ if (r.appInfo.compileSdkVersion == 0 || r.appInfo.compileSdkVersionCodename == null) {
+ // We don't know enough about this package. Abort!
+ return;
+ }
+
+ // If the application was built against an pre-release SDK that's older than the current
+ // platform OR if the current platform is pre-release and older than the SDK against which
+ // the application was built OR both are pre-release with the same SDK_INT but different
+ // codenames (e.g. simultaneous pre-release development), then we're likely to run into
+ // compatibility issues. Warn the user and offer to check for an update.
+ final int compileSdk = r.appInfo.compileSdkVersion;
+ final int platformSdk = Build.VERSION.SDK_INT;
+ final boolean isCompileSdkPreview = !"REL".equals(r.appInfo.compileSdkVersionCodename);
+ final boolean isPlatformSdkPreview = !"REL".equals(Build.VERSION.CODENAME);
+ if ((isCompileSdkPreview && compileSdk < platformSdk)
+ || (isPlatformSdkPreview && platformSdk < compileSdk)
+ || (isCompileSdkPreview && isPlatformSdkPreview && platformSdk == compileSdk
+ && !Build.VERSION.CODENAME.equals(r.appInfo.compileSdkVersionCodename))) {
+ mUiHandler.showUnsupportedCompileSdkDialog(r);
+ }
+ }
+
+ /**
+ * Called when an activity is being started.
+ *
+ * @param r record for the activity being started
+ */
+ public void onStartActivity(ActivityRecord r) {
+ showUnsupportedCompileSdkDialogIfNeeded(r);
+ showUnsupportedDisplaySizeDialogIfNeeded(r);
+ }
+
+ /**
+ * Called when an activity was previously started and is being resumed.
+ *
+ * @param r record for the activity being resumed
+ */
+ public void onResumeActivity(ActivityRecord r) {
+ showUnsupportedDisplaySizeDialogIfNeeded(r);
+ }
+
+ /**
+ * Called by ActivityManagerService when package data has been cleared.
+ *
+ * @param name the package whose data has been cleared
+ */
+ public void onPackageDataCleared(String name) {
+ removePackageAndHideDialogs(name);
+ }
+
+ /**
+ * Called by ActivityManagerService when a package has been uninstalled.
+ *
+ * @param name the package that has been uninstalled
+ */
+ public void onPackageUninstalled(String name) {
+ removePackageAndHideDialogs(name);
+ }
+
+ /**
+ * Called by ActivityManagerService when the default display density has changed.
+ */
+ public void onDensityChanged() {
+ mUiHandler.hideUnsupportedDisplaySizeDialog();
+ }
+
+ /**
+ * Does what it says on the tin.
+ */
+ private void removePackageAndHideDialogs(String name) {
+ mUiHandler.hideDialogsForPackage(name);
+
+ synchronized (mPackageFlags) {
+ mPackageFlags.remove(name);
+ mAmsHandler.scheduleWrite();
+ }
+ }
+
+ /**
+ * Hides the "unsupported display size" warning.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ */
+ @UiThread
+ private void hideUnsupportedDisplaySizeDialogUiThread() {
+ if (mUnsupportedDisplaySizeDialog != null) {
+ mUnsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialog = null;
+ }
+ }
+
+ /**
+ * Shows the "unsupported display size" warning for the given application.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param ar record for the activity that triggered the warning
+ */
+ @UiThread
+ private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) {
+ if (mUnsupportedDisplaySizeDialog != null) {
+ mUnsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialog = null;
+ }
+ if (ar != null && !hasPackageFlag(
+ ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) {
+ mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
+ AppWarnings.this, mUiContext, ar.info.applicationInfo);
+ mUnsupportedDisplaySizeDialog.show();
+ }
+ }
+
+ /**
+ * Shows the "unsupported compile SDK" warning for the given application.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param ar record for the activity that triggered the warning
+ */
+ @UiThread
+ private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) {
+ if (mUnsupportedCompileSdkDialog != null) {
+ mUnsupportedCompileSdkDialog.dismiss();
+ mUnsupportedCompileSdkDialog = null;
+ }
+ if (ar != null && !hasPackageFlag(
+ ar.packageName, FLAG_HIDE_COMPILE_SDK)) {
+ mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog(
+ AppWarnings.this, mUiContext, ar.info.applicationInfo);
+ mUnsupportedCompileSdkDialog.show();
+ }
+ }
+
+ /**
+ * Dismisses all warnings for the given package.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param name the package for which warnings should be dismissed, or {@code null} to dismiss
+ * all warnings
+ */
+ @UiThread
+ private void hideDialogsForPackageUiThread(String name) {
+ // Hides the "unsupported display" dialog if necessary.
+ if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals(
+ mUnsupportedDisplaySizeDialog.getPackageName()))) {
+ mUnsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialog = null;
+ }
+
+ // Hides the "unsupported compile SDK" dialog if necessary.
+ if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals(
+ mUnsupportedCompileSdkDialog.getPackageName()))) {
+ mUnsupportedCompileSdkDialog.dismiss();
+ mUnsupportedCompileSdkDialog = null;
+ }
+ }
+
+ /**
+ * Returns the value of the flag for the given package.
+ *
+ * @param name the package from which to retrieve the flag
+ * @param flag the bitmask for the flag to retrieve
+ * @return {@code true} if the flag is enabled, {@code false} otherwise
+ */
+ boolean hasPackageFlag(String name, int flag) {
+ return (getPackageFlags(name) & flag) == flag;
+ }
+
+ /**
+ * Sets the flag for the given package to the specified value.
+ *
+ * @param name the package on which to set the flag
+ * @param flag the bitmask for flag to set
+ * @param enabled the value to set for the flag
+ */
+ void setPackageFlag(String name, int flag, boolean enabled) {
+ synchronized (mPackageFlags) {
+ final int curFlags = getPackageFlags(name);
+ final int newFlags = enabled ? (curFlags & ~flag) : (curFlags | flag);
+ if (curFlags != newFlags) {
+ if (newFlags != 0) {
+ mPackageFlags.put(name, newFlags);
+ } else {
+ mPackageFlags.remove(name);
+ }
+ mAmsHandler.scheduleWrite();
+ }
+ }
+ }
+
+ /**
+ * Returns the bitmask of flags set for the specified package.
+ */
+ private int getPackageFlags(String name) {
+ synchronized (mPackageFlags) {
+ return mPackageFlags.getOrDefault(name, 0);
+ }
+ }
+
+ /**
+ * Handles messages on the system process UI thread.
+ */
+ private final class UiHandler extends Handler {
+ private static final int MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 1;
+ private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2;
+ private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3;
+ private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
+
+ public UiHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
+ final ActivityRecord ar = (ActivityRecord) msg.obj;
+ showUnsupportedDisplaySizeDialogUiThread(ar);
+ } break;
+ case MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
+ hideUnsupportedDisplaySizeDialogUiThread();
+ } break;
+ case MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG: {
+ final ActivityRecord ar = (ActivityRecord) msg.obj;
+ showUnsupportedCompileSdkDialogUiThread(ar);
+ } break;
+ case MSG_HIDE_DIALOGS_FOR_PACKAGE: {
+ final String name = (String) msg.obj;
+ hideDialogsForPackageUiThread(name);
+ } break;
+ }
+ }
+
+ public void showUnsupportedDisplaySizeDialog(ActivityRecord r) {
+ removeMessages(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+ obtainMessage(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG, r).sendToTarget();
+ }
+
+ public void hideUnsupportedDisplaySizeDialog() {
+ removeMessages(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+ sendEmptyMessage(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+ }
+
+ public void showUnsupportedCompileSdkDialog(ActivityRecord r) {
+ removeMessages(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG);
+ obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget();
+ }
+
+ public void hideDialogsForPackage(String name) {
+ obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget();
+ }
+ }
+
+ /**
+ * Handles messages on the ActivityManagerService thread.
+ */
+ private final class ConfigHandler extends Handler {
+ private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG;
+
+ private static final int DELAY_MSG_WRITE = 10000;
+
+ public ConfigHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_WRITE:
+ writeConfigToFileAmsThread();
+ break;
+ }
+ }
+
+ public void scheduleWrite() {
+ removeMessages(MSG_WRITE);
+ sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE);
+ }
+ }
+
+ /**
+ * Writes the configuration file.
+ * <p>
+ * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you
+ * don't care where you're doing I/O operations. But you <i>do</i> care, don't you?
+ */
+ private void writeConfigToFileAmsThread() {
+ // Create a shallow copy so that we don't have to synchronize on config.
+ final HashMap<String, Integer> packageFlags;
+ synchronized (mPackageFlags) {
+ packageFlags = new HashMap<>(mPackageFlags);
+ }
+
+ FileOutputStream fos = null;
+ try {
+ fos = mConfigFile.startWrite();
+
+ final XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ out.startTag(null, "packages");
+
+ for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) {
+ String pkg = entry.getKey();
+ int mode = entry.getValue();
+ if (mode == 0) {
+ continue;
+ }
+ out.startTag(null, "package");
+ out.attribute(null, "name", pkg);
+ out.attribute(null, "flags", Integer.toString(mode));
+ out.endTag(null, "package");
+ }
+
+ out.endTag(null, "packages");
+ out.endDocument();
+
+ mConfigFile.finishWrite(fos);
+ } catch (java.io.IOException e1) {
+ Slog.w(TAG, "Error writing package metadata", e1);
+ if (fos != null) {
+ mConfigFile.failWrite(fos);
+ }
+ }
+ }
+
+ /**
+ * Reads the configuration file and populates the package flags.
+ * <p>
+ * <strong>Note:</strong> Must be called from the constructor (and thus on the
+ * ActivityManagerService thread) since we don't synchronize on config.
+ */
+ private void readConfigFromFileAmsThread() {
+ FileInputStream fis = null;
+
+ try {
+ fis = mConfigFile.openRead();
+
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, StandardCharsets.UTF_8.name());
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG &&
+ eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+ if (eventType == XmlPullParser.END_DOCUMENT) {
+ return;
+ }
+
+ String tagName = parser.getName();
+ if ("packages".equals(tagName)) {
+ eventType = parser.next();
+ do {
+ if (eventType == XmlPullParser.START_TAG) {
+ tagName = parser.getName();
+ if (parser.getDepth() == 2) {
+ if ("package".equals(tagName)) {
+ final String name = parser.getAttributeValue(null, "name");
+ if (name != null) {
+ final String flags = parser.getAttributeValue(
+ null, "flags");
+ int flagsInt = 0;
+ if (flags != null) {
+ try {
+ flagsInt = Integer.parseInt(flags);
+ } catch (NumberFormatException e) {
+ }
+ }
+ mPackageFlags.put(name, flagsInt);
+ }
+ }
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+ }
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Error reading package metadata", e);
+ } catch (java.io.IOException e) {
+ if (fis != null) Slog.w(TAG, "Error reading package metadata", e);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (java.io.IOException e1) {
+ }
+ }
+ }
+ }
+}
diff --git a/com/android/server/am/ClientLifecycleManager.java b/com/android/server/am/ClientLifecycleManager.java
new file mode 100644
index 00000000..c04d103c
--- /dev/null
+++ b/com/android/server/am/ClientLifecycleManager.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 android.annotation.NonNull;
+import android.app.IApplicationThread;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.ActivityLifecycleItem;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * Class that is able to combine multiple client lifecycle transition requests and/or callbacks,
+ * and execute them as a single transaction.
+ *
+ * @see ClientTransaction
+ */
+class ClientLifecycleManager {
+ // TODO(lifecycler): Implement building transactions or global transaction.
+ // TODO(lifecycler): Use object pools for transactions and transaction items.
+
+ /**
+ * Schedule a transaction, which may consist of multiple callbacks and a lifecycle request.
+ * @param transaction A sequence of client transaction items.
+ * @throws RemoteException
+ *
+ * @see ClientTransaction
+ */
+ void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+ transaction.schedule();
+ }
+
+ /**
+ * Schedule a single lifecycle request or callback to client activity.
+ * @param client Target client.
+ * @param activityToken Target activity token.
+ * @param stateRequest A request to move target activity to a desired lifecycle state.
+ * @throws RemoteException
+ *
+ * @see ClientTransactionItem
+ */
+ void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken,
+ @NonNull ActivityLifecycleItem stateRequest) throws RemoteException {
+ final ClientTransaction clientTransaction = transactionWithState(client, activityToken,
+ stateRequest);
+ scheduleTransaction(clientTransaction);
+ }
+
+ /**
+ * Schedule a single callback delivery to client activity.
+ * @param client Target client.
+ * @param activityToken Target activity token.
+ * @param callback A request to deliver a callback.
+ * @throws RemoteException
+ *
+ * @see ClientTransactionItem
+ */
+ void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken,
+ @NonNull ClientTransactionItem callback) throws RemoteException {
+ final ClientTransaction clientTransaction = transactionWithCallback(client, activityToken,
+ callback);
+ scheduleTransaction(clientTransaction);
+ }
+
+ /**
+ * Schedule a single callback delivery to client application.
+ * @param client Target client.
+ * @param callback A request to deliver a callback.
+ * @throws RemoteException
+ *
+ * @see ClientTransactionItem
+ */
+ void scheduleTransaction(@NonNull IApplicationThread client,
+ @NonNull ClientTransactionItem callback) throws RemoteException {
+ final ClientTransaction clientTransaction = transactionWithCallback(client,
+ null /* activityToken */, callback);
+ scheduleTransaction(clientTransaction);
+ }
+
+ /**
+ * @return A new instance of {@link ClientTransaction} with a single lifecycle state request.
+ *
+ * @see ClientTransaction
+ * @see ClientTransactionItem
+ */
+ private static ClientTransaction transactionWithState(@NonNull IApplicationThread client,
+ @NonNull IBinder activityToken, @NonNull ActivityLifecycleItem stateRequest) {
+ final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken);
+ clientTransaction.setLifecycleStateRequest(stateRequest);
+ return clientTransaction;
+ }
+
+ /**
+ * @return A new instance of {@link ClientTransaction} with a single callback invocation.
+ *
+ * @see ClientTransaction
+ * @see ClientTransactionItem
+ */
+ private static ClientTransaction transactionWithCallback(@NonNull IApplicationThread client,
+ IBinder activityToken, @NonNull ClientTransactionItem callback) {
+ final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken);
+ clientTransaction.addCallback(callback);
+ return clientTransaction;
+ }
+}
diff --git a/com/android/server/am/CompatModePackages.java b/com/android/server/am/CompatModePackages.java
index bfc04565..65c4a421 100644
--- a/com/android/server/am/CompatModePackages.java
+++ b/com/android/server/am/CompatModePackages.java
@@ -58,8 +58,6 @@ public final class CompatModePackages {
public static final int COMPAT_FLAG_DONT_ASK = 1<<0;
// Compatibility state: compatibility mode is enabled.
public static final int COMPAT_FLAG_ENABLED = 1<<1;
- // Unsupported zoom state: don't warn the user about unsupported zoom mode.
- public static final int UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY = 1<<2;
private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
@@ -233,10 +231,6 @@ public final class CompatModePackages {
return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0;
}
- public boolean getPackageNotifyUnsupportedZoomLocked(String packageName) {
- return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0;
- }
-
public void setFrontActivityAskCompatModeLocked(boolean ask) {
ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
if (r != null) {
@@ -245,22 +239,12 @@ public final class CompatModePackages {
}
public void setPackageAskCompatModeLocked(String packageName, boolean ask) {
- int curFlags = getPackageFlags(packageName);
- int newFlags = ask ? (curFlags&~COMPAT_FLAG_DONT_ASK) : (curFlags|COMPAT_FLAG_DONT_ASK);
- if (curFlags != newFlags) {
- if (newFlags != 0) {
- mPackages.put(packageName, newFlags);
- } else {
- mPackages.remove(packageName);
- }
- scheduleWrite();
- }
+ setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask);
}
- public void setPackageNotifyUnsupportedZoomLocked(String packageName, boolean notify) {
+ private void setPackageFlagLocked(String packageName, int flag, boolean set) {
final int curFlags = getPackageFlags(packageName);
- final int newFlags = notify ? (curFlags&~UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) :
- (curFlags|UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY);
+ final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag);
if (curFlags != newFlags) {
if (newFlags != 0) {
mPackages.put(packageName, newFlags);
diff --git a/com/android/server/am/ConnectionRecord.java b/com/android/server/am/ConnectionRecord.java
index 9b7a0c43..6df283ca 100644
--- a/com/android/server/am/ConnectionRecord.java
+++ b/com/android/server/am/ConnectionRecord.java
@@ -19,6 +19,9 @@ package com.android.server.am;
import android.app.IServiceConnection;
import android.app.PendingIntent;
import android.content.Context;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.ConnectionRecordProto;
import java.io.PrintWriter;
@@ -119,4 +122,70 @@ final class ConnectionRecord {
sb.append('}');
return stringName = sb.toString();
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ if (binding == null) return; // if binding is null, don't write data, something is wrong.
+ long token = proto.start(fieldId);
+ proto.write(ConnectionRecordProto.HEX_HASH,
+ Integer.toHexString(System.identityHashCode(this)));
+ if (binding.client != null) {
+ proto.write(ConnectionRecordProto.USER_ID, binding.client.userId);
+ }
+ if ((flags&Context.BIND_AUTO_CREATE) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.AUTO_CREATE);
+ }
+ if ((flags&Context.BIND_DEBUG_UNBIND) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEBUG_UNBIND);
+ }
+ if ((flags&Context.BIND_NOT_FOREGROUND) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.NOT_FG);
+ }
+ if ((flags&Context.BIND_IMPORTANT_BACKGROUND) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.IMPORTANT_BG);
+ }
+ if ((flags&Context.BIND_ABOVE_CLIENT) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ABOVE_CLIENT);
+ }
+ if ((flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ALLOW_OOM_MANAGEMENT);
+ }
+ if ((flags&Context.BIND_WAIVE_PRIORITY) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.WAIVE_PRIORITY);
+ }
+ if ((flags&Context.BIND_IMPORTANT) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.IMPORTANT);
+ }
+ if ((flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ADJUST_WITH_ACTIVITY);
+ }
+ if ((flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.FG_SERVICE_WHILE_WAKE);
+ }
+ if ((flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.FG_SERVICE);
+ }
+ if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.TREAT_LIKE_ACTIVITY);
+ }
+ if ((flags&Context.BIND_VISIBLE) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.VISIBLE);
+ }
+ if ((flags&Context.BIND_SHOWING_UI) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.SHOWING_UI);
+ }
+ if ((flags&Context.BIND_NOT_VISIBLE) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.NOT_VISIBLE);
+ }
+ if (serviceDead) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEAD);
+ }
+ if (binding.service != null) {
+ proto.write(ConnectionRecordProto.SERVICE_NAME, binding.service.shortName);
+ }
+ if (conn != null) {
+ proto.write(ConnectionRecordProto.CONN_HEX_HASH,
+ Integer.toHexString(System.identityHashCode(conn.asBinder())));
+ }
+ proto.end(token);
+ }
}
diff --git a/com/android/server/am/IntentBindRecord.java b/com/android/server/am/IntentBindRecord.java
index be290e95..01ce64c6 100644
--- a/com/android/server/am/IntentBindRecord.java
+++ b/com/android/server/am/IntentBindRecord.java
@@ -21,6 +21,10 @@ import android.content.Intent;
import android.os.IBinder;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.AppBindRecordProto;
+import com.android.server.am.proto.IntentBindRecordProto;
import java.io.PrintWriter;
@@ -106,4 +110,32 @@ final class IntentBindRecord {
sb.append('}');
return stringName = sb.toString();
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(IntentBindRecordProto.HEX_HASH,
+ Integer.toHexString(System.identityHashCode(this)));
+ proto.write(IntentBindRecordProto.IS_CREATE,
+ (collectFlags()&Context.BIND_AUTO_CREATE) != 0);
+ if (intent != null) {
+ intent.getIntent().writeToProto(proto,
+ IntentBindRecordProto.INTENT, false, true, false, false);
+ }
+ if (binder != null) {
+ proto.write(IntentBindRecordProto.BINDER, binder.toString());
+ }
+ proto.write(IntentBindRecordProto.REQUESTED, requested);
+ proto.write(IntentBindRecordProto.RECEIVED, received);
+ proto.write(IntentBindRecordProto.HAS_BOUND, hasBound);
+ proto.write(IntentBindRecordProto.DO_REBIND, doRebind);
+
+ final int N = apps.size();
+ for (int i=0; i<N; i++) {
+ AppBindRecord a = apps.valueAt(i);
+ if (a != null) {
+ a.writeToProto(proto, IntentBindRecordProto.APPS);
+ }
+ }
+ proto.end(token);
+ }
}
diff --git a/com/android/server/am/KeyguardController.java b/com/android/server/am/KeyguardController.java
index 76b46796..35f4f253 100644
--- a/com/android/server/am/KeyguardController.java
+++ b/com/android/server/am/KeyguardController.java
@@ -19,9 +19,9 @@ package com.android.server.am;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
-import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
-import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
@@ -41,8 +41,11 @@ import android.os.RemoteException;
import android.os.Trace;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+
import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.WindowManagerService;
+
import java.io.PrintWriter;
/**
@@ -118,7 +121,7 @@ class KeyguardController {
/**
* Called when Keyguard is going away.
*
- * @param flags See {@link android.view.WindowManagerPolicy#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE}
+ * @param flags See {@link WindowManagerPolicy#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE}
* etc.
*/
void keyguardGoingAway(int flags) {
diff --git a/com/android/server/am/LaunchingActivityPositioner.java b/com/android/server/am/LaunchingActivityPositioner.java
index d5f9cf3a..793884d0 100644
--- a/com/android/server/am/LaunchingActivityPositioner.java
+++ b/com/android/server/am/LaunchingActivityPositioner.java
@@ -47,10 +47,10 @@ public class LaunchingActivityPositioner implements LaunchingBoundsPositioner {
return RESULT_SKIP;
}
- final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+ final Rect bounds = options.getLaunchBounds();
// Bounds weren't valid.
- if (bounds == null) {
+ if (bounds == null || bounds.isEmpty()) {
return RESULT_SKIP;
}
diff --git a/com/android/server/am/LaunchingBoundsController.java b/com/android/server/am/LaunchingBoundsController.java
index c762f7f4..5aa7f58f 100644
--- a/com/android/server/am/LaunchingBoundsController.java
+++ b/com/android/server/am/LaunchingBoundsController.java
@@ -101,8 +101,12 @@ class LaunchingBoundsController {
* @return {@code true} if bounds were set on the task. {@code false} otherwise.
*/
boolean layoutTask(TaskRecord task, WindowLayout layout) {
- calculateBounds(task, layout, null /*activity*/, null /*source*/, null /*options*/,
- mTmpRect);
+ return layoutTask(task, layout, null /*activity*/, null /*source*/, null /*options*/);
+ }
+
+ boolean layoutTask(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+ ActivityRecord source, ActivityOptions options) {
+ calculateBounds(task, layout, activity, source, options, mTmpRect);
if (mTmpRect.isEmpty()) {
return false;
diff --git a/com/android/server/am/LaunchingTaskPositioner.java b/com/android/server/am/LaunchingTaskPositioner.java
index c958fcac..d89568e2 100644
--- a/com/android/server/am/LaunchingTaskPositioner.java
+++ b/com/android/server/am/LaunchingTaskPositioner.java
@@ -88,7 +88,7 @@ class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoun
final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks();
- updateAvailableRect(task, mAvailableRect);
+ mAvailableRect.set(task.getParent().getBounds());
if (layout == null) {
positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
@@ -123,17 +123,6 @@ class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoun
return RESULT_CONTINUE;
}
- 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;
@@ -294,9 +283,9 @@ class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoun
private static boolean boundsConflict(Rect proposal, ArrayList<TaskRecord> tasks) {
for (int i = tasks.size() - 1; i >= 0; i--) {
- TaskRecord task = tasks.get(i);
- if (!task.mActivities.isEmpty() && task.mBounds != null) {
- Rect bounds = task.mBounds;
+ final TaskRecord task = tasks.get(i);
+ if (!task.mActivities.isEmpty() && !task.matchParentBounds()) {
+ final Rect bounds = task.getOverrideBounds();
if (closeLeftTopCorner(proposal, bounds) || closeRightTopCorner(proposal, bounds)
|| closeLeftBottomCorner(proposal, bounds)
|| closeRightBottomCorner(proposal, bounds)) {
diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java
index e87b4e63..d77e1a20 100644
--- a/com/android/server/am/LockTaskController.java
+++ b/com/android/server/am/LockTaskController.java
@@ -250,7 +250,24 @@ public class LockTaskController {
}
/**
- * @return whether the requested task is allowed to be launched.
+ * @return whether the requested task is allowed to be locked (either whitelisted, or declares
+ * lockTaskMode="always" in the manifest).
+ */
+ boolean isTaskWhitelisted(TaskRecord task) {
+ switch(task.mLockTaskAuth) {
+ case LOCK_TASK_AUTH_WHITELISTED:
+ case LOCK_TASK_AUTH_LAUNCHABLE:
+ case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
+ return true;
+ case LOCK_TASK_AUTH_PINNABLE:
+ case LOCK_TASK_AUTH_DONT_LOCK:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * @return whether the requested task is disallowed to be launched.
*/
boolean isLockTaskModeViolation(TaskRecord task) {
return isLockTaskModeViolation(task, false);
@@ -258,7 +275,7 @@ public class LockTaskController {
/**
* @param isNewClearTask whether the task would be cleared as part of the operation.
- * @return whether the requested task is allowed to be launched.
+ * @return whether the requested task is disallowed to be launched.
*/
boolean isLockTaskModeViolation(TaskRecord task, boolean isNewClearTask) {
if (isLockTaskModeViolationInternal(task, isNewClearTask)) {
@@ -275,21 +292,18 @@ public class LockTaskController {
// If the task is already at the top and won't be cleared, then allow the operation
return false;
}
- final int lockTaskAuth = task.mLockTaskAuth;
- switch (lockTaskAuth) {
- case LOCK_TASK_AUTH_DONT_LOCK:
- return !mLockTaskModeTasks.isEmpty();
- case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
- case LOCK_TASK_AUTH_LAUNCHABLE:
- case LOCK_TASK_AUTH_WHITELISTED:
- return false;
- case LOCK_TASK_AUTH_PINNABLE:
- // Pinnable tasks can't be launched on top of locktask tasks.
- return !mLockTaskModeTasks.isEmpty();
- default:
- Slog.w(TAG, "isLockTaskModeViolation: invalid lockTaskAuth value=" + lockTaskAuth);
- return true;
+
+ // Allow recents activity if enabled by policy
+ if (task.isActivityTypeRecents() && isRecentsAllowed(task.userId)) {
+ return false;
}
+
+ return !(isTaskWhitelisted(task) || mLockTaskModeTasks.isEmpty());
+ }
+
+ private boolean isRecentsAllowed(int userId) {
+ return (getLockTaskFeaturesForUser(userId)
+ & DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS) != 0;
}
/**
@@ -491,6 +505,7 @@ public class LockTaskController {
}
if (mLockTaskModeTasks.isEmpty()) {
+ mSupervisor.mRecentTasks.onLockTaskModeStateChanged(lockTaskModeState, task.userId);
// Start lock task on the handler thread
mHandler.post(() -> performStartLockTask(
task.intent.getComponent().getPackageName(),
diff --git a/com/android/server/am/ProcessList.java b/com/android/server/am/ProcessList.java
index 7810c5e4..6fb3dbb7 100644
--- a/com/android/server/am/ProcessList.java
+++ b/com/android/server/am/ProcessList.java
@@ -40,7 +40,7 @@ import android.view.Display;
/**
* Activity manager code dealing with processes.
*/
-final class ProcessList {
+public final class ProcessList {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
// The minimum time we allow between crashes, for us to consider this
@@ -400,6 +400,9 @@ final class ProcessList {
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
procState = "CACC";
break;
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+ procState = "CRE ";
+ break;
case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
procState = "CEM ";
break;
@@ -494,6 +497,7 @@ final class ProcessList {
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -515,6 +519,7 @@ final class ProcessList {
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -536,6 +541,7 @@ final class ProcessList {
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -557,6 +563,7 @@ final class ProcessList {
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -578,6 +585,7 @@ final class ProcessList {
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
diff --git a/com/android/server/am/ProcessRecord.java b/com/android/server/am/ProcessRecord.java
index e8477231..9d3c2ae3 100644
--- a/com/android/server/am/ProcessRecord.java
+++ b/com/android/server/am/ProcessRecord.java
@@ -164,6 +164,8 @@ final class ProcessRecord {
// all activities running in the process
final ArrayList<ActivityRecord> activities = new ArrayList<>();
+ // any tasks this process had run root activities in
+ final ArrayList<TaskRecord> recentTasks = new ArrayList<>();
// all ServiceRecord running in this process
final ArraySet<ServiceRecord> services = new ArraySet<>();
// services that are currently executing code (need to remain foreground).
@@ -396,6 +398,12 @@ final class ProcessRecord {
pw.print(prefix); pw.print(" - "); pw.println(activities.get(i));
}
}
+ if (recentTasks.size() > 0) {
+ pw.print(prefix); pw.println("Recent Tasks:");
+ for (int i=0; i<recentTasks.size(); i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(recentTasks.get(i));
+ }
+ }
if (services.size() > 0) {
pw.print(prefix); pw.println("Services:");
for (int i=0; i<services.size(); i++) {
@@ -512,6 +520,13 @@ final class ProcessRecord {
}
}
+ public void clearRecentTasks() {
+ for (int i = recentTasks.size() - 1; i >= 0; i--) {
+ recentTasks.get(i).clearRootProcess();
+ }
+ recentTasks.clear();
+ }
+
/**
* This method returns true if any of the activities within the process record are interesting
* to the user. See HistoryRecord.isInterestingToUserLocked()
diff --git a/com/android/server/am/ProviderMap.java b/com/android/server/am/ProviderMap.java
index 32d03dae..8a905f8c 100644
--- a/com/android/server/am/ProviderMap.java
+++ b/com/android/server/am/ProviderMap.java
@@ -28,6 +28,7 @@ import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -322,8 +323,7 @@ public final class ProviderMap {
return needSep;
}
- protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
- int opti, boolean dumpAll) {
+ private ArrayList<ContentProviderRecord> getProvidersForName(String name) {
ArrayList<ContentProviderRecord> allProviders = new ArrayList<ContentProviderRecord>();
ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
@@ -365,6 +365,12 @@ public final class ProviderMap {
}
}
}
+ return providers;
+ }
+
+ protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
+ int opti, boolean dumpAll) {
+ ArrayList<ContentProviderRecord> providers = getProvidersForName(name);
if (providers.size() <= 0) {
return false;
@@ -417,6 +423,33 @@ public final class ProviderMap {
}
/**
+ * Similar to the dumpProvider, but only dumps the first matching provider.
+ * The provider is responsible for dumping as proto.
+ */
+ protected boolean dumpProviderProto(FileDescriptor fd, PrintWriter pw, String name,
+ String[] args) {
+ //add back the --proto arg, which was stripped out by PriorityDump
+ String[] newArgs = Arrays.copyOf(args, args.length + 1);
+ newArgs[args.length] = "--proto";
+
+ ArrayList<ContentProviderRecord> providers = getProvidersForName(name);
+
+ if (providers.size() <= 0) {
+ return false;
+ }
+
+ // Only dump the first provider, since we are dumping in proto format
+ for (int i = 0; i < providers.size(); i++) {
+ final ContentProviderRecord r = providers.get(i);
+ if (r.proc != null && r.proc.thread != null) {
+ dumpToTransferPipe(null, fd, pw, r, newArgs);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Invokes IApplicationThread.dumpProvider() on the thread of the specified provider without
* any meta string (e.g., provider info, indentation) written to the file descriptor.
*/
diff --git a/com/android/server/am/RecentTasks.java b/com/android/server/am/RecentTasks.java
index d35c37b5..abb296e9 100644
--- a/com/android/server/am/RecentTasks.java
+++ b/com/android/server/am/RecentTasks.java
@@ -80,6 +80,20 @@ import java.util.concurrent.TimeUnit;
/**
* Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the
* least recent.
+ *
+ * The trimming logic can be boiled down to the following. For recent task list with a number of
+ * tasks, the visible tasks are an interleaving subset of tasks that would normally be presented to
+ * the user. Non-visible tasks are not considered for trimming. Of the visible tasks, only a
+ * sub-range are presented to the user, based on the device type, last task active time, or other
+ * task state. Tasks that are not in the visible range and are not returnable from the SystemUI
+ * (considering the back stack) are considered trimmable. If the device does not support recent
+ * tasks, then trimming is completely disabled.
+ *
+ * eg.
+ * L = [TTTTTTTTTTTTTTTTTTTTTTTTTT] // list of tasks
+ * [VVV VV VVVV V V V ] // Visible tasks
+ * [RRR RR XXXX X X X ] // Visible range tasks, eg. if the device only shows 5 tasks,
+ * // 'X' tasks are trimmed.
*/
class RecentTasks {
private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM;
@@ -488,6 +502,18 @@ class RecentTasks {
}
}
+ void onLockTaskModeStateChanged(int lockTaskModeState, int userId) {
+ if (lockTaskModeState != ActivityManager.LOCK_TASK_MODE_LOCKED) {
+ return;
+ }
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final TaskRecord tr = mTasks.get(i);
+ if (tr.userId == userId && !mService.mLockTaskController.isTaskWhitelisted(tr)) {
+ remove(tr);
+ }
+ }
+ }
+
void removeTasksByPackageName(String packageName, int userId) {
for (int i = mTasks.size() - 1; i >= 0; --i) {
final TaskRecord tr = mTasks.get(i);
@@ -496,7 +522,8 @@ class RecentTasks {
if (tr.userId != userId) return;
if (!taskPackageName.equals(packageName)) return;
- mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
+ mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS,
+ "remove-package-task");
}
}
@@ -513,7 +540,7 @@ class RecentTasks {
&& (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
if (sameComponent) {
mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
- REMOVE_FROM_RECENTS);
+ REMOVE_FROM_RECENTS, "disabled-package");
}
}
}
@@ -1001,12 +1028,13 @@ class RecentTasks {
continue;
} else {
numVisibleTasks++;
- if (isInVisibleRange(task, numVisibleTasks)) {
+ if (isInVisibleRange(task, numVisibleTasks) || !isTrimmable(task)) {
// Keep visible tasks in range
i++;
continue;
} else {
- // Fall through to trim visible tasks that are no longer in range
+ // Fall through to trim visible tasks that are no longer in range and
+ // trimmable
if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
"Trimming out-of-range visible task=" + task);
}
@@ -1122,6 +1150,28 @@ class RecentTasks {
}
/**
+ * @return whether the given task can be trimmed even if it is outside the visible range.
+ */
+ protected boolean isTrimmable(TaskRecord task) {
+ final ActivityStack stack = task.getStack();
+ final ActivityStack homeStack = mService.mStackSupervisor.mHomeStack;
+
+ // No stack for task, just trim it
+ if (stack == null) {
+ return true;
+ }
+
+ // Ignore tasks from different displays
+ if (stack.getDisplay() != homeStack.getDisplay()) {
+ return false;
+ }
+
+ // Trim tasks that are in stacks that are behind the home stack
+ final ActivityDisplay display = stack.getDisplay();
+ return display.getIndexOf(stack) < display.getIndexOf(homeStack);
+ }
+
+ /**
* If needed, remove oldest existing entries in recents that are for the same kind
* of task as the given one.
*/
@@ -1426,9 +1476,6 @@ class RecentTasks {
* Creates a new RecentTaskInfo from a TaskRecord.
*/
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;
@@ -1444,8 +1491,8 @@ class RecentTasks {
rti.affiliatedTaskId = tr.mAffiliatedTaskId;
rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
rti.numActivities = 0;
- if (tr.mBounds != null) {
- rti.bounds = new Rect(tr.mBounds);
+ if (!tr.matchParentBounds()) {
+ rti.bounds = new Rect(tr.getOverrideBounds());
}
rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode();
rti.resizeMode = tr.mResizeMode;
diff --git a/com/android/server/am/ServiceRecord.java b/com/android/server/am/ServiceRecord.java
index 16995e50..b6eff003 100644
--- a/com/android/server/am/ServiceRecord.java
+++ b/com/android/server/am/ServiceRecord.java
@@ -19,6 +19,7 @@ package com.android.server.am;
import com.android.internal.app.procstats.ServiceState;
import com.android.internal.os.BatteryStatsImpl;
import com.android.server.LocalServices;
+import com.android.server.am.proto.ServiceRecordProto;
import com.android.server.notification.NotificationManagerInternal;
import android.app.INotificationManager;
@@ -42,6 +43,8 @@ import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -79,7 +82,7 @@ final class ServiceRecord extends Binder {
final String permission;// permission needed to access service
final boolean exported; // from ServiceInfo.exported
final Runnable restarter; // used to schedule retries of starting the service
- final long createTime; // when this service was created
+ final long createRealTime; // when this service was created
final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings
= new ArrayMap<Intent.FilterComparison, IntentBindRecord>();
// All active bindings to the service.
@@ -103,7 +106,7 @@ final class ServiceRecord extends Binder {
boolean startRequested; // someone explicitly called start?
boolean delayedStop; // service has been stopped but is in a delayed start?
boolean stopIfKilled; // last onStart() said to stop if service killed?
- boolean callStart; // last onStart() has asked to alway be called on restart.
+ boolean callStart; // last onStart() has asked to always be called on restart.
int executeNesting; // number of outstanding operations keeping foreground.
boolean executeFg; // should we be executing in the foreground?
long executingStart; // start time of last execute request.
@@ -159,6 +162,27 @@ final class ServiceRecord extends Binder {
}
}
+ public void writeToProto(ProtoOutputStream proto, long fieldId, long now) {
+ long token = proto.start(fieldId);
+ proto.write(ServiceRecordProto.StartItemProto.ID, id);
+ ProtoUtils.toDuration(proto,
+ ServiceRecordProto.StartItemProto.DURATION, deliveredTime, now);
+ proto.write(ServiceRecordProto.StartItemProto.DELIVERY_COUNT, deliveryCount);
+ proto.write(ServiceRecordProto.StartItemProto.DONE_EXECUTING_COUNT, doneExecutingCount);
+ if (intent != null) {
+ intent.writeToProto(proto, ServiceRecordProto.StartItemProto.INTENT, true, true,
+ true, false);
+ }
+ if (neededGrants != null) {
+ neededGrants.writeToProto(proto, ServiceRecordProto.StartItemProto.NEEDED_GRANTS);
+ }
+ if (uriPermissions != null) {
+ uriPermissions.writeToProto(proto,
+ ServiceRecordProto.StartItemProto.URI_PERMISSIONS);
+ }
+ proto.end(token);
+ }
+
public String toString() {
if (stringName != null) {
return stringName;
@@ -209,6 +233,117 @@ final class ServiceRecord extends Binder {
}
}
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(ServiceRecordProto.SHORT_NAME, this.shortName);
+ proto.write(ServiceRecordProto.HEX_HASH,
+ Integer.toHexString(System.identityHashCode(this)));
+ proto.write(ServiceRecordProto.IS_RUNNING, app != null);
+ if (app != null) {
+ proto.write(ServiceRecordProto.PID, app.pid);
+ }
+ if (intent != null) {
+ intent.getIntent().writeToProto(proto, ServiceRecordProto.INTENT, false, true, false,
+ true);
+ }
+ proto.write(ServiceRecordProto.PACKAGE_NAME, packageName);
+ proto.write(ServiceRecordProto.PROCESS_NAME, processName);
+ proto.write(ServiceRecordProto.PERMISSION, permission);
+
+ long now = SystemClock.uptimeMillis();
+ long nowReal = SystemClock.elapsedRealtime();
+ if (appInfo != null) {
+ long appInfoToken = proto.start(ServiceRecordProto.APPINFO);
+ proto.write(ServiceRecordProto.AppInfo.BASE_DIR, appInfo.sourceDir);
+ if (!Objects.equals(appInfo.sourceDir, appInfo.publicSourceDir)) {
+ proto.write(ServiceRecordProto.AppInfo.RES_DIR, appInfo.publicSourceDir);
+ }
+ proto.write(ServiceRecordProto.AppInfo.DATA_DIR, appInfo.dataDir);
+ proto.end(appInfoToken);
+ }
+ if (app != null) {
+ app.writeToProto(proto, ServiceRecordProto.APP);
+ }
+ if (isolatedProc != null) {
+ isolatedProc.writeToProto(proto, ServiceRecordProto.ISOLATED_PROC);
+ }
+ proto.write(ServiceRecordProto.WHITELIST_MANAGER, whitelistManager);
+ proto.write(ServiceRecordProto.DELAYED, delayed);
+ if (isForeground || foregroundId != 0) {
+ long fgToken = proto.start(ServiceRecordProto.FOREGROUND);
+ proto.write(ServiceRecordProto.Foreground.ID, foregroundId);
+ foregroundNoti.writeToProto(proto, ServiceRecordProto.Foreground.NOTIFICATION);
+ proto.end(fgToken);
+ }
+ ProtoUtils.toDuration(proto, ServiceRecordProto.CREATE_REAL_TIME, createRealTime, nowReal);
+ ProtoUtils.toDuration(proto,
+ ServiceRecordProto.STARTING_BG_TIMEOUT, startingBgTimeout, now);
+ ProtoUtils.toDuration(proto, ServiceRecordProto.LAST_ACTIVITY_TIME, lastActivity, now);
+ ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
+ proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
+
+ if (startRequested || delayedStop || lastStartId != 0) {
+ long startToken = proto.start(ServiceRecordProto.START);
+ proto.write(ServiceRecordProto.Start.START_REQUESTED, startRequested);
+ proto.write(ServiceRecordProto.Start.DELAYED_STOP, delayedStop);
+ proto.write(ServiceRecordProto.Start.STOP_IF_KILLED, stopIfKilled);
+ proto.write(ServiceRecordProto.Start.LAST_START_ID, lastStartId);
+ proto.end(startToken);
+ }
+
+ if (executeNesting != 0) {
+ long executNestingToken = proto.start(ServiceRecordProto.EXECUTE);
+ proto.write(ServiceRecordProto.ExecuteNesting.EXECUTE_NESTING, executeNesting);
+ proto.write(ServiceRecordProto.ExecuteNesting.EXECUTE_FG, executeFg);
+ ProtoUtils.toDuration(proto,
+ ServiceRecordProto.ExecuteNesting.EXECUTING_START, executingStart, now);
+ proto.end(executNestingToken);
+ }
+ if (destroying || destroyTime != 0) {
+ ProtoUtils.toDuration(proto, ServiceRecordProto.DESTORY_TIME, destroyTime, now);
+ }
+ if (crashCount != 0 || restartCount != 0 || restartDelay != 0 || nextRestartTime != 0) {
+ long crashToken = proto.start(ServiceRecordProto.CRASH);
+ proto.write(ServiceRecordProto.Crash.RESTART_COUNT, restartCount);
+ ProtoUtils.toDuration(proto, ServiceRecordProto.Crash.RESTART_DELAY, restartDelay, now);
+ ProtoUtils.toDuration(proto,
+ ServiceRecordProto.Crash.NEXT_RESTART_TIME, nextRestartTime, now);
+ proto.write(ServiceRecordProto.Crash.CRASH_COUNT, crashCount);
+ proto.end(crashToken);
+ }
+
+ if (deliveredStarts.size() > 0) {
+ final int N = deliveredStarts.size();
+ for (int i = 0; i < N; i++) {
+ deliveredStarts.get(i).writeToProto(proto,
+ ServiceRecordProto.DELIVERED_STARTS, now);
+ }
+ }
+ if (pendingStarts.size() > 0) {
+ final int N = pendingStarts.size();
+ for (int i = 0; i < N; i++) {
+ pendingStarts.get(i).writeToProto(proto, ServiceRecordProto.PENDING_STARTS, now);
+ }
+ }
+ if (bindings.size() > 0) {
+ final int N = bindings.size();
+ for (int i=0; i<N; i++) {
+ IntentBindRecord b = bindings.valueAt(i);
+ b.writeToProto(proto, ServiceRecordProto.BINDINGS);
+ }
+ }
+ if (connections.size() > 0) {
+ final int N = connections.size();
+ for (int conni=0; conni<N; conni++) {
+ ArrayList<ConnectionRecord> c = connections.valueAt(conni);
+ for (int i=0; i<c.size(); i++) {
+ c.get(i).writeToProto(proto, ServiceRecordProto.CONNECTIONS);
+ }
+ }
+ }
+ proto.end(token);
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("intent={");
pw.print(intent.getIntent().toShortString(false, true, false, true));
@@ -243,7 +378,7 @@ final class ServiceRecord extends Binder {
pw.print(" foregroundNoti="); pw.println(foregroundNoti);
}
pw.print(prefix); pw.print("createTime=");
- TimeUtils.formatDuration(createTime, nowReal, pw);
+ TimeUtils.formatDuration(createRealTime, nowReal, pw);
pw.print(" startingBgTimeout=");
TimeUtils.formatDuration(startingBgTimeout, now, pw);
pw.println();
@@ -329,7 +464,7 @@ final class ServiceRecord extends Binder {
permission = sInfo.permission;
exported = sInfo.exported;
this.restarter = restarter;
- createTime = SystemClock.elapsedRealtime();
+ createRealTime = SystemClock.elapsedRealtime();
lastActivity = SystemClock.uptimeMillis();
userId = UserHandle.getUserId(appInfo.uid);
createdFromFg = callerIsFg;
diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java
index 949f51fe..83965ee4 100644
--- a/com/android/server/am/TaskRecord.java
+++ b/com/android/server/am/TaskRecord.java
@@ -19,9 +19,6 @@ 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.INVALID_STACK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -257,6 +254,11 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
/** Current stack. Setter must always be used to update the value. */
private ActivityStack mStack;
+ /** The process that had previously hosted the root activity of this task.
+ * Used to know that we should try harder to keep this process around, in case the
+ * user wants to return to it. */
+ private ProcessRecord mRootProcess;
+
/** Takes on same value as first root activity */
boolean isPersistable = false;
int maxRecents;
@@ -288,11 +290,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
final ActivityManagerService mService;
- // Whether or not this task covers the entire screen; by default tasks are fullscreen.
- boolean mFullscreen = true;
-
- // Bounds of the Task. null for fullscreen tasks.
- Rect mBounds = null;
private final Rect mTmpStableBounds = new Rect();
private final Rect mTmpNonDecorBounds = new Rect();
private final Rect mTmpRect = new Rect();
@@ -475,68 +472,76 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
boolean resize(Rect bounds, int resizeMode, boolean preserveWindow, boolean deferResume) {
- if (!isResizeable()) {
- Slog.w(TAG, "resizeTask: task " + this + " not resizeable.");
- return true;
- }
-
- // If this is a forced resize, let it go through even if the bounds is not changing,
- // as we might need a relayout due to surface size change (to/from fullscreen).
- final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0;
- if (Objects.equals(mBounds, bounds) && !forced) {
- // Nothing to do here...
- return true;
- }
- bounds = validateBounds(bounds);
+ mService.mWindowManager.deferSurfaceLayout();
- if (mWindowContainerController == null) {
- // Task doesn't exist in window manager yet (e.g. was restored from recents).
- // 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 (!inFreeformWindowingMode()) {
- // re-restore the task so it can have the proper stack association.
- mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
+ try {
+ if (!isResizeable()) {
+ Slog.w(TAG, "resizeTask: task " + this + " not resizeable.");
+ return true;
}
- return true;
- }
- if (!canResizeToBounds(bounds)) {
- throw new IllegalArgumentException("resizeTask: Can not resize task=" + this
- + " to bounds=" + bounds + " resizeMode=" + mResizeMode);
- }
+ // If this is a forced resize, let it go through even if the bounds is not changing,
+ // as we might need a relayout due to surface size change (to/from fullscreen).
+ final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0;
+ if (equivalentOverrideBounds(bounds) && !forced) {
+ // Nothing to do here...
+ return true;
+ }
- // Do not move the task to another stack here.
- // This method assumes that the task is already placed in the right stack.
- // we do not mess with that decision and we only do the resize!
+ if (mWindowContainerController == null) {
+ // Task doesn't exist in window manager yet (e.g. was restored from recents).
+ // 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 (!inFreeformWindowingMode()) {
+ // re-restore the task so it can have the proper stack association.
+ mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
+ }
+ return true;
+ }
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + taskId);
+ if (!canResizeToBounds(bounds)) {
+ throw new IllegalArgumentException("resizeTask: Can not resize task=" + this
+ + " to bounds=" + bounds + " resizeMode=" + mResizeMode);
+ }
- final boolean updatedConfig = updateOverrideConfiguration(bounds);
- // This variable holds information whether the configuration didn't change in a significant
- // way and the activity was kept the way it was. If it's false, it means the activity had
- // to be relaunched due to configuration change.
- boolean kept = true;
- if (updatedConfig) {
- final ActivityRecord r = topRunningActivityLocked();
- if (r != null && !deferResume) {
- kept = r.ensureActivityConfigurationLocked(0 /* globalChanges */, preserveWindow);
- mService.mStackSupervisor.ensureActivitiesVisibleLocked(r, 0, !PRESERVE_WINDOWS);
- if (!kept) {
- mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ // Do not move the task to another stack here.
+ // This method assumes that the task is already placed in the right stack.
+ // we do not mess with that decision and we only do the resize!
+
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + taskId);
+
+ final boolean updatedConfig = updateOverrideConfiguration(bounds);
+ // This variable holds information whether the configuration didn't change in a significant
+
+ // way and the activity was kept the way it was. If it's false, it means the activity
+ // had
+ // to be relaunched due to configuration change.
+ boolean kept = true;
+ if (updatedConfig) {
+ final ActivityRecord r = topRunningActivityLocked();
+ if (r != null && !deferResume) {
+ kept = r.ensureActivityConfigurationLocked(0 /* globalChanges */,
+ preserveWindow);
+ mService.mStackSupervisor.ensureActivitiesVisibleLocked(r, 0,
+ !PRESERVE_WINDOWS);
+ if (!kept) {
+ mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ }
}
}
- }
- mWindowContainerController.resize(mBounds, getOverrideConfiguration(), kept, forced);
+ mWindowContainerController.resize(kept, forced);
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
- return kept;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ return kept;
+ } finally {
+ mService.mWindowManager.continueSurfaceLayout();
+ }
}
// TODO: Investigate combining with the resize() method above.
void resizeWindowContainer() {
- mWindowContainerController.resize(mBounds, getOverrideConfiguration(), false /* relayout */,
- false /* forced */);
+ mWindowContainerController.resize(false /* relayout */, false /* forced */);
}
void getWindowContainerBounds(Rect bounds) {
@@ -681,16 +686,17 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
// Make sure the task has the appropriate bounds/size for the stack it is in.
final boolean toStackSplitScreenPrimary =
toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+ final Rect configBounds = getOverrideBounds();
if ((toStackWindowingMode == WINDOWING_MODE_FULLSCREEN
|| toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)
- && !Objects.equals(mBounds, toStack.mBounds)) {
- kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
+ && !Objects.equals(configBounds, toStack.getOverrideBounds())) {
+ kept = resize(toStack.getOverrideBounds(), RESIZE_MODE_SYSTEM, !mightReplaceWindow,
deferResume);
} else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) {
Rect bounds = getLaunchBounds();
if (bounds == null) {
mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
- bounds = mBounds;
+ bounds = configBounds;
}
kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
} else if (toStackSplitScreenPrimary || toStackWindowingMode == WINDOWING_MODE_PINNED) {
@@ -699,7 +705,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
// mode
mService.mStackSupervisor.moveRecentsStackToFront(reason);
}
- kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
+ kept = resize(toStack.getOverrideBounds(), RESIZE_MODE_SYSTEM, !mightReplaceWindow,
deferResume);
}
} finally {
@@ -962,6 +968,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
mService.notifyTaskPersisterLocked(this, false);
}
+ clearRootProcess();
+
// TODO: Use window container controller once tasks are better synced between AM and WM
mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId);
}
@@ -1303,7 +1311,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
* Completely remove all activities associated with an existing
* task starting at a specified index.
*/
- final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately) {
+ final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately,
+ String reason) {
int numActivities = mActivities.size();
for ( ; activityNdx < numActivities; ++activityNdx) {
final ActivityRecord r = mActivities.get(activityNdx);
@@ -1317,7 +1326,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
--activityNdx;
--numActivities;
} else if (mStack.finishActivityLocked(r, Activity.RESULT_CANCELED, null,
- "clear-task-index", false, pauseImmediately)) {
+ reason, false, pauseImmediately)) {
--activityNdx;
--numActivities;
}
@@ -1329,7 +1338,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
*/
void performClearTaskLocked() {
mReuseTask = true;
- performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY);
+ performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY, "clear-task-all");
mReuseTask = false;
}
@@ -1400,9 +1409,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
return null;
}
- void removeTaskActivitiesLocked(boolean pauseImmediately) {
+ void removeTaskActivitiesLocked(boolean pauseImmediately, String reason) {
// Just remove the entire task.
- performClearTaskAtIndexLocked(0, pauseImmediately);
+ performClearTaskAtIndexLocked(0, pauseImmediately, reason);
}
String lockTaskAuthToString() {
@@ -1494,8 +1503,10 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
return true;
}
final boolean landscape = bounds.width() > bounds.height();
+ final Rect configBounds = getOverrideBounds();
if (mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION) {
- return mBounds == null || landscape == (mBounds.width() > mBounds.height());
+ return configBounds.isEmpty()
+ || landscape == (configBounds.width() > configBounds.height());
}
return (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY || !landscape)
&& (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY || landscape);
@@ -1616,6 +1627,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
final int effectiveRootIndex = findEffectiveRootIndex();
final ActivityRecord r = mActivities.get(effectiveRootIndex);
setIntent(r);
+
+ // Update the task description when the activities change
+ updateTaskDescription();
}
void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
@@ -1913,8 +1927,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
return;
}
+ final Rect configBounds = getOverrideBounds();
if (adjustWidth) {
- if (mBounds != null && bounds.right == mBounds.right) {
+ if (!configBounds.isEmpty() && bounds.right == configBounds.right) {
bounds.left = bounds.right - minWidth;
} else {
// Either left bounds match, or neither match, or the previous bounds were
@@ -1923,7 +1938,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
}
if (adjustHeight) {
- if (mBounds != null && bounds.bottom == mBounds.bottom) {
+ if (!configBounds.isEmpty() && bounds.bottom == configBounds.bottom) {
bounds.top = bounds.bottom - minHeight;
} else {
// Either top bounds match, or neither match, or the previous bounds were
@@ -1970,42 +1985,45 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
* @return True if the override configuration was updated.
*/
boolean updateOverrideConfiguration(Rect bounds, @Nullable Rect insetBounds) {
- if (Objects.equals(mBounds, bounds)) {
+ if (equivalentOverrideBounds(bounds)) {
return false;
}
+ final Rect currentBounds = getOverrideBounds();
+
mTmpConfig.setTo(getOverrideConfiguration());
- final boolean oldFullscreen = mFullscreen;
final Configuration newConfig = getOverrideConfiguration();
- mFullscreen = bounds == null;
+ final boolean matchParentBounds = bounds == null || bounds.isEmpty();
final boolean persistBounds = getWindowConfiguration().persistTaskBounds();
- if (mFullscreen) {
- if (mBounds != null && persistBounds) {
- mLastNonFullscreenBounds = mBounds;
+ if (matchParentBounds) {
+ if (!currentBounds.isEmpty() && persistBounds) {
+ mLastNonFullscreenBounds = currentBounds;
}
- mBounds = null;
+ setBounds(null);
newConfig.unset();
} else {
mTmpRect.set(bounds);
adjustForMinimalTaskDimensions(mTmpRect);
- if (mBounds == null) {
- mBounds = new Rect(mTmpRect);
- } else {
- mBounds.set(mTmpRect);
- }
+ setBounds(mTmpRect);
+
if (mStack == null || persistBounds) {
- mLastNonFullscreenBounds = mBounds;
+ mLastNonFullscreenBounds = getOverrideBounds();
}
computeOverrideConfiguration(newConfig, mTmpRect, insetBounds,
mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
}
onOverrideConfigurationChanged(newConfig);
+ return !mTmpConfig.equals(newConfig);
+ }
- if (mFullscreen != oldFullscreen) {
+ @Override
+ public void onConfigurationChanged(Configuration newParentConfig) {
+ final boolean wasInMultiWindowMode = inMultiWindowMode();
+ super.onConfigurationChanged(newParentConfig);
+ if (wasInMultiWindowMode != inMultiWindowMode()) {
mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this);
}
-
- return !mTmpConfig.equals(newConfig);
+ // TODO: Should also take care of Pip mode changes here.
}
/** Clears passed config and fills it with new override values. */
@@ -2046,23 +2064,19 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp);
final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);
config.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize);
-
}
Rect updateOverrideConfigurationFromLaunchBounds() {
- final Rect bounds = validateBounds(getLaunchBounds());
+ final Rect bounds = getLaunchBounds();
updateOverrideConfiguration(bounds);
- if (bounds != null) {
- bounds.set(mBounds);
+ if (bounds != null && !bounds.isEmpty()) {
+ // TODO: Review if we actually want to do this - we are setting the launch bounds
+ // directly here.
+ bounds.set(getOverrideBounds());
}
return bounds;
}
- static Rect validateBounds(Rect bounds) {
- // TODO: Not needed once we have bounds in WindowConfiguration.
- return (bounds != null && bounds.isEmpty()) ? null : bounds;
- }
-
/** Updates the task's bounds and override configuration to match what is expected for the
* input stack. */
void updateOverrideConfigurationForStack(ActivityStack inStack) {
@@ -2075,7 +2089,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
throw new IllegalArgumentException("Can not position non-resizeable task="
+ this + " in stack=" + inStack);
}
- if (mBounds != null) {
+ if (!matchParentBounds()) {
return;
}
if (mLastNonFullscreenBounds != null) {
@@ -2084,7 +2098,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
}
} else {
- updateOverrideConfiguration(inStack.mBounds);
+ updateOverrideConfiguration(inStack.getOverrideBounds());
}
}
@@ -2098,9 +2112,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
if (!isActivityTypeStandardOrUndefined()
|| windowingMode == WINDOWING_MODE_FULLSCREEN
|| (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !isResizeable())) {
- return isResizeable() ? mStack.mBounds : null;
+ return isResizeable() ? mStack.getOverrideBounds() : null;
} else if (!getWindowConfiguration().persistTaskBounds()) {
- return mStack.mBounds;
+ return mStack.getOverrideBounds();
}
return mLastNonFullscreenBounds;
}
@@ -2114,6 +2128,22 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
}
+ void setRootProcess(ProcessRecord proc) {
+ clearRootProcess();
+ if (intent != null &&
+ (intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
+ mRootProcess = proc;
+ proc.recentTasks.add(this);
+ }
+ }
+
+ void clearRootProcess() {
+ if (mRootProcess != null) {
+ mRootProcess.recentTasks.remove(this);
+ mRootProcess = null;
+ }
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("userId="); pw.print(userId);
pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
@@ -2198,6 +2228,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
if (lastDescription != null) {
pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription);
}
+ if (mRootProcess != null) {
+ pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
+ }
pw.print(prefix); pw.print("stackId="); pw.println(getStackId());
pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
@@ -2261,9 +2294,12 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
proto.write(ACTIVITY_TYPE, getActivityType());
proto.write(RESIZE_MODE, mResizeMode);
- proto.write(FULLSCREEN, mFullscreen);
- if (mBounds != null) {
- mBounds.writeToProto(proto, BOUNDS);
+ // TODO: Remove, no longer needed with windowingMode.
+ proto.write(FULLSCREEN, matchParentBounds());
+
+ if (!matchParentBounds()) {
+ final Rect bounds = getOverrideBounds();
+ bounds.writeToProto(proto, BOUNDS);
}
proto.write(MIN_WIDTH, mMinWidth);
proto.write(MIN_HEIGHT, mMinHeight);
diff --git a/com/android/server/am/UnsupportedCompileSdkDialog.java b/com/android/server/am/UnsupportedCompileSdkDialog.java
new file mode 100644
index 00000000..b6f6ae6b
--- /dev/null
+++ b/com/android/server/am/UnsupportedCompileSdkDialog.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+
+import com.android.internal.R;
+import com.android.server.utils.AppInstallerUtil;
+
+public class UnsupportedCompileSdkDialog {
+ private final AlertDialog mDialog;
+ private final String mPackageName;
+
+ public UnsupportedCompileSdkDialog(final AppWarnings manager, Context context,
+ ApplicationInfo appInfo) {
+ mPackageName = appInfo.packageName;
+
+ final PackageManager pm = context.getPackageManager();
+ final CharSequence label = appInfo.loadSafeLabel(pm);
+ final CharSequence message = context.getString(R.string.unsupported_compile_sdk_message,
+ label);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context)
+ .setPositiveButton(R.string.ok, null)
+ .setMessage(message)
+ .setView(R.layout.unsupported_compile_sdk_dialog_content);
+
+ // If we might be able to update the app, show a button.
+ final Intent installerIntent = AppInstallerUtil.createIntent(context, appInfo.packageName);
+ if (installerIntent != null) {
+ builder.setNeutralButton(R.string.unsupported_compile_sdk_check_update,
+ (dialog, which) -> context.startActivity(installerIntent));
+ }
+
+ // Ensure the content view is prepared.
+ mDialog = builder.create();
+ mDialog.create();
+
+ final Window window = mDialog.getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_PHONE);
+
+ // DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
+ window.getAttributes().setTitle("UnsupportedCompileSdkDialog");
+
+ final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
+ alwaysShow.setChecked(true);
+ alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
+ mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked));
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public void show() {
+ mDialog.show();
+ }
+
+ public void dismiss() {
+ mDialog.dismiss();
+ }
+}
diff --git a/com/android/server/am/UnsupportedDisplaySizeDialog.java b/com/android/server/am/UnsupportedDisplaySizeDialog.java
index 501cd6bb..88506632 100644
--- a/com/android/server/am/UnsupportedDisplaySizeDialog.java
+++ b/com/android/server/am/UnsupportedDisplaySizeDialog.java
@@ -30,7 +30,7 @@ public class UnsupportedDisplaySizeDialog {
private final AlertDialog mDialog;
private final String mPackageName;
- public UnsupportedDisplaySizeDialog(final ActivityManagerService service, Context context,
+ public UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context,
ApplicationInfo appInfo) {
mPackageName = appInfo.packageName;
@@ -54,14 +54,10 @@ public class UnsupportedDisplaySizeDialog {
// DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
window.getAttributes().setTitle("UnsupportedDisplaySizeDialog");
- final CheckBox alwaysShow = (CheckBox) mDialog.findViewById(R.id.ask_checkbox);
+ final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
alwaysShow.setChecked(true);
- alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> {
- synchronized (service) {
- service.mCompatModePackages.setPackageNotifyUnsupportedZoomLocked(
- mPackageName, isChecked);
- }
- });
+ alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
+ mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked));
}
public String getPackageName() {
diff --git a/com/android/server/am/UriPermissionOwner.java b/com/android/server/am/UriPermissionOwner.java
index 28344df2..fc07c1ab 100644
--- a/com/android/server/am/UriPermissionOwner.java
+++ b/com/android/server/am/UriPermissionOwner.java
@@ -20,6 +20,9 @@ import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.UriPermissionOwnerProto;
import com.google.android.collect.Sets;
@@ -139,6 +142,26 @@ final class UriPermissionOwner {
}
}
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(UriPermissionOwnerProto.OWNER, owner.toString());
+ if (mReadPerms != null) {
+ synchronized (mReadPerms) {
+ for (UriPermission p : mReadPerms) {
+ p.uri.writeToProto(proto, UriPermissionOwnerProto.READ_PERMS);
+ }
+ }
+ }
+ if (mWritePerms != null) {
+ synchronized (mWritePerms) {
+ for (UriPermission p : mWritePerms) {
+ p.uri.writeToProto(proto, UriPermissionOwnerProto.WRITE_PERMS);
+ }
+ }
+ }
+ proto.end(token);
+ }
+
@Override
public String toString() {
return owner.toString();
diff --git a/com/android/server/am/UserController.java b/com/android/server/am/UserController.java
index 2df5dc93..4e3d8d27 100644
--- a/com/android/server/am/UserController.java
+++ b/com/android/server/am/UserController.java
@@ -89,6 +89,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemServiceManager;
import com.android.server.pm.UserManagerService;
@@ -355,27 +356,35 @@ class UserController implements Handler.Callback {
// Only keep marching forward if user is actually unlocked
if (!StorageManager.isUserKeyUnlocked(userId)) return;
synchronized (mLock) {
- // Bail if we ended up with a stale user
- if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
-
- // Do not proceed if unexpected state
- if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
+ // Do not proceed if unexpected state or a stale user
+ if (mStartedUsers.get(userId) != uss || uss.state != STATE_RUNNING_LOCKED) {
return;
}
}
- mInjector.getUserManagerInternal().setUserState(userId, uss.state);
uss.mUnlockProgress.start();
// Prepare app storage before we go any further
uss.mUnlockProgress.setProgress(5,
mInjector.getContext().getString(R.string.android_start_title));
- mInjector.getUserManager().onBeforeUnlockUser(userId);
- uss.mUnlockProgress.setProgress(20);
- // Dispatch unlocked to system services; when fully dispatched,
- // that calls through to the next "unlocked" phase
- mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
- .sendToTarget();
+ // Call onBeforeUnlockUser on a worker thread that allows disk I/O
+ FgThread.getHandler().post(() -> {
+ mInjector.getUserManager().onBeforeUnlockUser(userId);
+ synchronized (mLock) {
+ // Do not proceed if unexpected state
+ if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
+ return;
+ }
+ }
+ mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+
+ uss.mUnlockProgress.setProgress(20);
+
+ // Dispatch unlocked to system services; when fully dispatched,
+ // that calls through to the next "unlocked" phase
+ mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
+ .sendToTarget();
+ });
}
/**
@@ -1819,7 +1828,10 @@ class UserController implements Handler.Callback {
case SYSTEM_USER_UNLOCK_MSG:
final int userId = msg.arg1;
mInjector.getSystemServiceManager().unlockUser(userId);
- mInjector.loadUserRecents(userId);
+ // Loads recents on a worker thread that allows disk I/O
+ FgThread.getHandler().post(() -> {
+ mInjector.loadUserRecents(userId);
+ });
if (userId == UserHandle.USER_SYSTEM) {
mInjector.startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
}
diff --git a/com/android/server/autofill/RemoteFillService.java b/com/android/server/autofill/RemoteFillService.java
index af55807f..831c488e 100644
--- a/com/android/server/autofill/RemoteFillService.java
+++ b/com/android/server/autofill/RemoteFillService.java
@@ -26,6 +26,7 @@ import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
@@ -100,7 +101,8 @@ final class RemoteFillService implements DeathRecipient {
@NonNull String servicePackageName);
void onFillRequestFailure(@Nullable CharSequence message,
@NonNull String servicePackageName);
- void onSaveRequestSuccess(@NonNull String servicePackageName);
+ void onSaveRequestSuccess(@NonNull String servicePackageName,
+ @Nullable IntentSender intentSender);
void onSaveRequestFailure(@Nullable CharSequence message,
@NonNull String servicePackageName);
void onServiceDied(RemoteFillService service);
@@ -308,10 +310,11 @@ final class RemoteFillService implements DeathRecipient {
});
}
- private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest) {
+ private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest,
+ IntentSender intentSender) {
mHandler.getHandler().post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
- mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName());
+ mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), intentSender);
}
});
}
@@ -624,12 +627,13 @@ final class RemoteFillService implements DeathRecipient {
mCallback = new ISaveCallback.Stub() {
@Override
- public void onSuccess() {
+ public void onSuccess(IntentSender intentSender) {
if (!finish()) return;
final RemoteFillService remoteService = getService();
if (remoteService != null) {
- remoteService.dispatchOnSaveRequestSuccess(PendingSaveRequest.this);
+ remoteService.dispatchOnSaveRequestSuccess(PendingSaveRequest.this,
+ intentSender);
}
}
diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java
index af4668a6..99b92b9c 100644
--- a/com/android/server/autofill/Session.java
+++ b/com/android/server/autofill/Session.java
@@ -561,7 +561,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// FillServiceCallbacks
@Override
- public void onSaveRequestSuccess(@NonNull String servicePackageName) {
+ public void onSaveRequestSuccess(@NonNull String servicePackageName,
+ @Nullable IntentSender intentSender) {
synchronized (mLock) {
mIsSaving = false;
@@ -572,8 +573,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
- .setType(MetricsEvent.TYPE_SUCCESS);
+ .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN);
mMetricsLogger.write(log);
+ if (intentSender != null) {
+ if (sDebug) Slog.d(TAG, "Starting intent sender on save()");
+ startIntentSender(intentSender);
+ }
// Nothing left to do...
removeSelf();
@@ -1167,7 +1172,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
break;
}
}
+
value = getSanitizedValue(sanitizers, id, value);
+ if (value == null) {
+ if (sDebug) {
+ Slog.d(TAG, "value of required field " + id + " failed sanitization");
+ }
+ allRequiredAreNotEmpty = false;
+ break;
+ }
+ viewState.setSanitizedValue(value);
currentValues.put(id, value);
final AutofillValue filledValue = viewState.getAutofilledValue();
@@ -1337,7 +1351,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return sanitizers;
}
- @NonNull
+ @Nullable
private AutofillValue getSanitizedValue(
@Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers,
@NonNull AutofillId id,
@@ -1431,10 +1445,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context);
for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) {
- final ViewState state = mViewStates.valueAt(viewStateNum);
+ final ViewState viewState = mViewStates.valueAt(viewStateNum);
- final AutofillId id = state.id;
- final AutofillValue value = state.getCurrentValue();
+ final AutofillId id = viewState.id;
+ final AutofillValue value = viewState.getCurrentValue();
if (value == null) {
if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + id);
continue;
@@ -1446,9 +1460,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
- final AutofillValue sanitizedValue = getSanitizedValue(sanitizers, id, value);
+ AutofillValue sanitizedValue = viewState.getSanitizedValue();
- node.updateAutofillValue(sanitizedValue);
+ if (sanitizedValue == null) {
+ // Field is optional and haven't been sanitized yet.
+ sanitizedValue = getSanitizedValue(sanitizers, id, value);
+ }
+ if (sanitizedValue != null) {
+ node.updateAutofillValue(sanitizedValue);
+ } else if (sDebug) {
+ Slog.d(TAG, "Not updating field " + id + " because it failed sanitization");
+ }
}
// Sanitize structure before it's sent to service.
diff --git a/com/android/server/autofill/ViewState.java b/com/android/server/autofill/ViewState.java
index 832a66b2..0dbdc13e 100644
--- a/com/android/server/autofill/ViewState.java
+++ b/com/android/server/autofill/ViewState.java
@@ -76,6 +76,7 @@ final class ViewState {
private FillResponse mResponse;
private AutofillValue mCurrentValue;
private AutofillValue mAutofilledValue;
+ private AutofillValue mSanitizedValue;
private Rect mVirtualBounds;
private int mState;
private String mDatasetId;
@@ -117,6 +118,15 @@ final class ViewState {
}
@Nullable
+ AutofillValue getSanitizedValue() {
+ return mSanitizedValue;
+ }
+
+ void setSanitizedValue(@Nullable AutofillValue value) {
+ mSanitizedValue = value;
+ }
+
+ @Nullable
FillResponse getResponse() {
return mResponse;
}
@@ -218,6 +228,7 @@ final class ViewState {
}
pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue);
pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue);
+ pw.print(prefix); pw.print("sanitizedValue:" ); pw.println(mSanitizedValue);
pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
}
} \ No newline at end of file
diff --git a/com/android/server/backup/RefactoredBackupManagerService.java b/com/android/server/backup/RefactoredBackupManagerService.java
index a45a4f0c..2788218d 100644
--- a/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/com/android/server/backup/RefactoredBackupManagerService.java
@@ -48,7 +48,6 @@ import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
import android.app.backup.ISelectBackupTransportCallback;
-import android.app.backup.SelectBackupTransportCallback;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -103,6 +102,7 @@ import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.internal.BackupHandler;
import com.android.server.backup.internal.BackupRequest;
import com.android.server.backup.internal.ClearDataObserver;
+import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
import com.android.server.backup.internal.PerformInitializeTask;
import com.android.server.backup.internal.ProvisionedObserver;
@@ -117,6 +117,7 @@ import com.android.server.backup.params.ClearRetryParams;
import com.android.server.backup.params.RestoreParams;
import com.android.server.backup.restore.ActiveRestoreSession;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.transport.TransportClient;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
@@ -1585,8 +1586,27 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
}
+ // We're using pieces of the new binding on-demand infra-structure and the old always-bound
+ // infra-structure below this comment. The TransportManager.getCurrentTransportClient() line
+ // is using the new one and TransportManager.getCurrentTransportBinder() is using the old.
+ // This is weird but there is a reason.
+ // This is the natural place to put TransportManager.getCurrentTransportClient() because of
+ // the null handling below that should be the same for TransportClient.
+ // TransportClient.connect() would return a IBackupTransport for us (instead of using the
+ // old infra), but it may block and we don't want this in this thread.
+ // The only usage of transport in this method is for transport.transportDirName(). When the
+ // push-from-transport part of binding on-demand is in place we will replace the calls for
+ // IBackupTransport.transportDirName() with calls for
+ // TransportManager.transportDirName(transportName) or similar. So we'll leave the old piece
+ // here until we implement that.
+ // TODO(brufino): Remove always-bound code mTransportManager.getCurrentTransportBinder()
+ TransportClient transportClient =
+ mTransportManager.getCurrentTransportClient("BMS.requestBackup()");
IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
- if (transport == null) {
+ if (transportClient == null || transport == null) {
+ if (transportClient != null) {
+ mTransportManager.disposeOfTransportClient(transportClient, "BMS.requestBackup()");
+ }
BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL,
@@ -1594,6 +1614,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
return BackupManager.ERROR_TRANSPORT_ABORTED;
}
+ OnTaskFinishedListener listener =
+ caller -> mTransportManager.disposeOfTransportClient(transportClient, caller);
+
ArrayList<String> fullBackupList = new ArrayList<>();
ArrayList<String> kvBackupList = new ArrayList<>();
for (String packageName : packages) {
@@ -1640,8 +1663,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0;
Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
- msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer,
- monitor, true, nonIncrementalBackup);
+ msg.obj = new BackupParams(transportClient, dirName, kvBackupList, fullBackupList, observer,
+ monitor, listener, true, nonIncrementalBackup);
mBackupHandler.sendMessage(msg);
return BackupManager.SUCCESS;
}
@@ -2135,8 +2158,17 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
mFullBackupQueue.remove(0);
CountDownLatch latch = new CountDownLatch(1);
String[] pkg = new String[]{entry.packageName};
- mRunningFullBackupTask = new PerformFullTransportBackupTask(this, null, pkg, true,
- scheduledJob, latch, null, null, false /* userInitiated */);
+ mRunningFullBackupTask = PerformFullTransportBackupTask.newWithCurrentTransport(
+ this,
+ /* observer */ null,
+ pkg,
+ /* updateSchedule */ true,
+ scheduledJob,
+ latch,
+ /* backupObserver */ null,
+ /* monitor */ null,
+ /* userInitiated */ false,
+ "BMS.beginFullBackup()");
// Acquiring wakelock for PerformFullTransportBackupTask before its start.
mWakelock.acquire();
(new Thread(mRunningFullBackupTask)).start();
@@ -2490,8 +2522,17 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
final long oldId = Binder.clearCallingIdentity();
try {
CountDownLatch latch = new CountDownLatch(1);
- PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(this, null,
- pkgNames, false, null, latch, null, null, false /* userInitiated */);
+ Runnable task = PerformFullTransportBackupTask.newWithCurrentTransport(
+ this,
+ /* observer */ null,
+ pkgNames,
+ /* updateSchedule */ false,
+ /* runningJob */ null,
+ latch,
+ /* backupObserver */ null,
+ /* monitor */ null,
+ /* userInitiated */ false,
+ "BMS.fullTransportBackup()");
// Acquiring wakelock for PerformFullTransportBackupTask before its start.
mWakelock.acquire();
(new Thread(task, "full-transport-master")).start();
diff --git a/com/android/server/backup/TransportManager.java b/com/android/server/backup/TransportManager.java
index 7a0173f6..a2b5cb88 100644
--- a/com/android/server/backup/TransportManager.java
+++ b/com/android/server/backup/TransportManager.java
@@ -16,8 +16,9 @@
package com.android.server.backup;
+import android.annotation.Nullable;
import android.app.backup.BackupManager;
-import android.app.backup.SelectBackupTransportCallback;
+import android.app.backup.BackupTransport;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -44,9 +45,11 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportClientManager;
+import com.android.server.backup.transport.TransportConnectionListener;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -60,8 +63,7 @@ public class TransportManager {
private static final String TAG = "BackupTransportManager";
@VisibleForTesting
- /* package */ static final String SERVICE_ACTION_TRANSPORT_HOST =
- "android.backup.TRANSPORT_HOST";
+ public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec
private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins
@@ -72,6 +74,7 @@ public class TransportManager {
private final PackageManager mPackageManager;
private final Set<ComponentName> mTransportWhitelist;
private final Handler mHandler;
+ private final TransportClientManager mTransportClientManager;
/**
* This listener is called after we bind to any transport. If it returns true, this is a valid
@@ -95,6 +98,10 @@ public class TransportManager {
@GuardedBy("mTransportLock")
private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
+ /** Names of transports we've bound to at least once */
+ @GuardedBy("mTransportLock")
+ private final Map<String, ComponentName> mTransportsByName = new ArrayMap<>();
+
/**
* Callback interface for {@link #ensureTransportReady(ComponentName, TransportReadyCallback)}.
*/
@@ -123,6 +130,7 @@ public class TransportManager {
mCurrentTransportName = defaultTransport;
mTransportBoundListener = listener;
mHandler = new RebindOnTimeoutHandler(looper);
+ mTransportClientManager = new TransportClientManager(context);
}
void onPackageAdded(String packageName) {
@@ -204,6 +212,67 @@ public class TransportManager {
return null;
}
+ /**
+ * Returns the transport name associated with {@param transportClient} or {@code null} if not
+ * found.
+ */
+ @Nullable
+ public String getTransportName(TransportClient transportClient) {
+ ComponentName transportComponent = transportClient.getTransportComponent();
+ synchronized (mTransportLock) {
+ for (Map.Entry<String, ComponentName> transportEntry : mTransportsByName.entrySet()) {
+ if (transportEntry.getValue().equals(transportComponent)) {
+ return transportEntry.getKey();
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Returns a {@link TransportClient} for {@param transportName} or {@code null} if not found.
+ *
+ * @param transportName The name of the transport as returned by {@link BackupTransport#name()}.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient} or null if not found.
+ */
+ @Nullable
+ public TransportClient getTransportClient(String transportName, String caller) {
+ ComponentName transportComponent = mTransportsByName.get(transportName);
+ if (transportComponent == null) {
+ Slog.w(TAG, "Transport " + transportName + " not registered");
+ return null;
+ }
+ return mTransportClientManager.getTransportClient(transportComponent, caller);
+ }
+
+ /**
+ * Returns a {@link TransportClient} for the current transport or null if not found.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient} or null if not found.
+ */
+ @Nullable
+ public TransportClient getCurrentTransportClient(String caller) {
+ return getTransportClient(mCurrentTransportName, caller);
+ }
+
+ /**
+ * Disposes of the {@link TransportClient}.
+ *
+ * @param transportClient The {@link TransportClient} to be disposed of.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ public void disposeOfTransportClient(TransportClient transportClient, String caller) {
+ mTransportClientManager.disposeOfTransportClient(transportClient, caller);
+ }
+
String[] getBoundTransportNames() {
synchronized (mTransportLock) {
return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]);
@@ -374,6 +443,7 @@ public class TransportManager {
String componentShortString = component.flattenToShortString().intern();
if (success) {
Slog.d(TAG, "Bound to transport: " + componentShortString);
+ mTransportsByName.put(mTransportName, component);
mBoundTransports.put(mTransportName, component);
for (TransportReadyCallback listener : mListeners) {
listener.onSuccess(mTransportName);
@@ -528,7 +598,7 @@ public class TransportManager {
// These only exists to make it testable with Robolectric, which is not updated to API level 24
// yet.
// TODO: Get rid of this once Robolectric is updated.
- private static UserHandle createSystemUserHandle() {
+ public static UserHandle createSystemUserHandle() {
return new UserHandle(UserHandle.USER_SYSTEM);
}
}
diff --git a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 90134e1a..d5b3d98f 100644
--- a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -24,6 +24,7 @@ import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_B
import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT;
import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL;
+import android.annotation.Nullable;
import android.app.IBackupAgent;
import android.app.backup.BackupManager;
import android.app.backup.BackupManagerMonitor;
@@ -46,7 +47,11 @@ import com.android.server.EventLogTags;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.FullBackupJob;
import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.TransportManager;
+import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
@@ -89,6 +94,36 @@ import java.util.concurrent.atomic.AtomicLong;
* mBackupRunner.getBackupResultBlocking().
*/
public class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask {
+ public static PerformFullTransportBackupTask newWithCurrentTransport(
+ RefactoredBackupManagerService backupManagerService,
+ IFullBackupRestoreObserver observer,
+ String[] whichPackages,
+ boolean updateSchedule,
+ FullBackupJob runningJob,
+ CountDownLatch latch,
+ IBackupObserver backupObserver,
+ IBackupManagerMonitor monitor,
+ boolean userInitiated,
+ String caller) {
+ TransportManager transportManager = backupManagerService.getTransportManager();
+ TransportClient transportClient = transportManager.getCurrentTransportClient(caller);
+ OnTaskFinishedListener listener =
+ listenerCaller ->
+ transportManager.disposeOfTransportClient(transportClient, listenerCaller);
+ return new PerformFullTransportBackupTask(
+ backupManagerService,
+ transportClient,
+ observer,
+ whichPackages,
+ updateSchedule,
+ runningJob,
+ latch,
+ backupObserver,
+ monitor,
+ listener,
+ userInitiated);
+ }
+
private static final String TAG = "PFTBT";
private RefactoredBackupManagerService backupManagerService;
@@ -102,9 +137,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
IBackupObserver mBackupObserver;
IBackupManagerMonitor mMonitor;
boolean mUserInitiated;
- private volatile IBackupTransport mTransport;
SinglePackageBackupRunner mBackupRunner;
private final int mBackupRunnerOpToken;
+ private final OnTaskFinishedListener mListener;
+ private final TransportClient mTransportClient;
// This is true when a backup operation for some package is in progress.
private volatile boolean mIsDoingBackup;
@@ -112,18 +148,22 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
private final int mCurrentOpToken;
public PerformFullTransportBackupTask(RefactoredBackupManagerService backupManagerService,
+ TransportClient transportClient,
IFullBackupRestoreObserver observer,
String[] whichPackages, boolean updateSchedule,
FullBackupJob runningJob, CountDownLatch latch, IBackupObserver backupObserver,
- IBackupManagerMonitor monitor, boolean userInitiated) {
+ IBackupManagerMonitor monitor, @Nullable OnTaskFinishedListener listener,
+ boolean userInitiated) {
super(observer);
this.backupManagerService = backupManagerService;
+ mTransportClient = transportClient;
mUpdateSchedule = updateSchedule;
mLatch = latch;
mJob = runningJob;
mPackages = new ArrayList<>(whichPackages.length);
mBackupObserver = backupObserver;
mMonitor = monitor;
+ mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP;
mUserInitiated = userInitiated;
mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
mBackupRunnerOpToken = backupManagerService.generateRandomIntegerToken();
@@ -241,8 +281,11 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
if (mIsDoingBackup) {
backupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll);
try {
- mTransport.cancelFullBackup();
- } catch (RemoteException e) {
+ // If we're running a backup we should be connected to a transport
+ IBackupTransport transport =
+ mTransportClient.getConnectedTransport("PFTBT.handleCancel()");
+ transport.cancelFullBackup();
+ } catch (RemoteException | TransportNotAvailableException e) {
Slog.w(TAG, "Error calling cancelFullBackup() on transport: " + e);
// Can't do much.
}
@@ -291,8 +334,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
return;
}
- mTransport = backupManagerService.getTransportManager().getCurrentTransportBinder();
- if (mTransport == null) {
+ IBackupTransport transport = mTransportClient.connect("PFTBT.run()");
+ if (transport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
@@ -325,17 +368,17 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
if (mCancelAll) {
break;
}
- backupPackageStatus = mTransport.performFullBackup(currentPackage,
+ backupPackageStatus = transport.performFullBackup(currentPackage,
transportPipes[0], flags);
if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
- quota = mTransport.getBackupQuota(currentPackage.packageName,
+ quota = transport.getBackupQuota(currentPackage.packageName,
true /* isFullBackup */);
// Now set up the backup engine / data source end of things
enginePipes = ParcelFileDescriptor.createPipe();
mBackupRunner =
new SinglePackageBackupRunner(enginePipes[1], currentPackage,
- mTransport, quota, mBackupRunnerOpToken);
+ mTransportClient, quota, mBackupRunnerOpToken);
// The runner dup'd the pipe half, so we close it here
enginePipes[1].close();
enginePipes[1] = null;
@@ -389,7 +432,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
out.write(buffer, 0, nRead);
synchronized (mCancelLock) {
if (!mCancelAll) {
- backupPackageStatus = mTransport.sendBackupData(nRead);
+ backupPackageStatus = transport.sendBackupData(nRead);
}
}
totalRead += nRead;
@@ -425,12 +468,12 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
// result based on what finishBackup() returns. If we're in a
// failure case already, preserve that result and ignore whatever
// finishBackup() reports.
- final int finishResult = mTransport.finishBackup();
+ final int finishResult = transport.finishBackup();
if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
backupPackageStatus = finishResult;
}
} else {
- mTransport.cancelFullBackup();
+ transport.cancelFullBackup();
}
}
}
@@ -469,7 +512,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
// Also ask the transport how long it wants us to wait before
// moving on to the next package, if any.
- backoff = mTransport.requestFullBackupTime();
+ backoff = transport.requestFullBackupTime();
if (DEBUG_SCHEDULING) {
Slog.i(TAG, "Transport suggested backoff=" + backoff);
}
@@ -591,6 +634,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
backupManagerService.setRunningFullBackupTask(null);
}
+ mListener.onFinished("PFTBT.run()");
+
mLatch.countDown();
// Now that we're actually done with schedule-driven work, reschedule
@@ -633,12 +678,13 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
class SinglePackageBackupPreflight implements BackupRestoreTask, FullBackupPreflight {
final AtomicLong mResult = new AtomicLong(BackupTransport.AGENT_ERROR);
final CountDownLatch mLatch = new CountDownLatch(1);
- final IBackupTransport mTransport;
+ final TransportClient mTransportClient;
final long mQuota;
private final int mCurrentOpToken;
- SinglePackageBackupPreflight(IBackupTransport transport, long quota, int currentOpToken) {
- mTransport = transport;
+ SinglePackageBackupPreflight(
+ TransportClient transportClient, long quota, int currentOpToken) {
+ mTransportClient = transportClient;
mQuota = quota;
mCurrentOpToken = currentOpToken;
}
@@ -672,7 +718,9 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
Slog.v(TAG, "Got preflight response; size=" + totalSize);
}
- result = mTransport.checkFullBackupSize(totalSize);
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow("PFTBT$SPBP.preflightFullBackup()");
+ result = transport.checkFullBackupSize(totalSize);
if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
if (MORE_DEBUG) {
Slog.d(TAG, "Package hit quota limit on preflight " +
@@ -739,12 +787,13 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
private volatile boolean mIsCancelled;
SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
- IBackupTransport transport, long quota, int currentOpToken) throws IOException {
+ TransportClient transportClient, long quota, int currentOpToken)
+ throws IOException {
mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
mTarget = target;
mCurrentOpToken = currentOpToken;
mEphemeralToken = backupManagerService.generateRandomIntegerToken();
- mPreflight = new SinglePackageBackupPreflight(transport, quota, mEphemeralToken);
+ mPreflight = new SinglePackageBackupPreflight(transportClient, quota, mEphemeralToken);
mPreflightLatch = new CountDownLatch(1);
mBackupLatch = new CountDownLatch(1);
mPreflightResult = BackupTransport.AGENT_ERROR;
diff --git a/com/android/server/backup/internal/BackupHandler.java b/com/android/server/backup/internal/BackupHandler.java
index 8f823004..9011b95c 100644
--- a/com/android/server/backup/internal/BackupHandler.java
+++ b/com/android/server/backup/internal/BackupHandler.java
@@ -38,6 +38,8 @@ import com.android.server.EventLogTags;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.DataChangedJournal;
import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.TransportManager;
import com.android.server.backup.fullbackup.PerformAdbBackupTask;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.params.AdbBackupParams;
@@ -51,10 +53,8 @@ import com.android.server.backup.params.RestoreParams;
import com.android.server.backup.restore.PerformAdbRestoreTask;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
-import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
/**
* Asynchronous backup/restore handler thread.
@@ -81,7 +81,7 @@ public class BackupHandler extends Handler {
public static final int MSG_BACKUP_RESTORE_STEP = 20;
public static final int MSG_OP_COMPLETE = 21;
- private RefactoredBackupManagerService backupManagerService;
+ private final RefactoredBackupManagerService backupManagerService;
public BackupHandler(
RefactoredBackupManagerService backupManagerService, Looper looper) {
@@ -91,13 +91,23 @@ public class BackupHandler extends Handler {
public void handleMessage(Message msg) {
+ TransportManager transportManager = backupManagerService.getTransportManager();
switch (msg.what) {
case MSG_RUN_BACKUP: {
backupManagerService.setLastBackupPass(System.currentTimeMillis());
+ String callerLogString = "BH/MSG_RUN_BACKUP";
+ TransportClient transportClient =
+ transportManager.getCurrentTransportClient(callerLogString);
IBackupTransport transport =
- backupManagerService.getTransportManager().getCurrentTransportBinder();
+ transportClient != null
+ ? transportClient.connect(callerLogString)
+ : null;
if (transport == null) {
+ if (transportClient != null) {
+ transportManager
+ .disposeOfTransportClient(transportClient, callerLogString);
+ }
Slog.v(TAG, "Backup requested but no transport available");
synchronized (backupManagerService.getQueueLock()) {
backupManagerService.setBackupRunning(false);
@@ -138,9 +148,13 @@ public class BackupHandler extends Handler {
// Spin up a backup state sequence and set it running
try {
String dirName = transport.transportDirName();
+ OnTaskFinishedListener listener =
+ caller ->
+ transportManager
+ .disposeOfTransportClient(transportClient, caller);
PerformBackupTask pbt = new PerformBackupTask(
- backupManagerService, transport, dirName, queue,
- oldJournal, null, null, Collections.<String>emptyList(), false,
+ backupManagerService, transportClient, dirName, queue,
+ oldJournal, null, null, listener, Collections.emptyList(), false,
false /* nonIncremental */);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
@@ -157,6 +171,7 @@ public class BackupHandler extends Handler {
}
if (!staged) {
+ transportManager.disposeOfTransportClient(transportClient, callerLogString);
// if we didn't actually hand off the wakelock, rewind until next time
synchronized (backupManagerService.getQueueLock()) {
backupManagerService.setBackupRunning(false);
@@ -382,9 +397,9 @@ public class BackupHandler extends Handler {
PerformBackupTask pbt = new PerformBackupTask(
backupManagerService,
- params.transport, params.dirName,
- kvQueue, null, params.observer, params.monitor, params.fullPackages, true,
- params.nonIncrementalBackup);
+ params.transportClient, params.dirName,
+ kvQueue, null, params.observer, params.monitor, params.listener,
+ params.fullPackages, true, params.nonIncrementalBackup);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
break;
diff --git a/com/android/server/backup/internal/OnTaskFinishedListener.java b/com/android/server/backup/internal/OnTaskFinishedListener.java
new file mode 100644
index 00000000..e417f06c
--- /dev/null
+++ b/com/android/server/backup/internal/OnTaskFinishedListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.internal;
+
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportConnectionListener;
+
+/** Listener to be called when a task finishes, successfully or not. */
+public interface OnTaskFinishedListener {
+ OnTaskFinishedListener NOP = caller -> {};
+
+ /**
+ * Called when a task finishes, successfully or not.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ void onFinished(String caller);
+}
diff --git a/com/android/server/backup/internal/PerformBackupTask.java b/com/android/server/backup/internal/PerformBackupTask.java
index c0caa557..5be1b390 100644
--- a/com/android/server/backup/internal/PerformBackupTask.java
+++ b/com/android/server/backup/internal/PerformBackupTask.java
@@ -63,6 +63,8 @@ import com.android.server.backup.KeyValueBackupJob;
import com.android.server.backup.PackageManagerBackupAgent;
import com.android.server.backup.RefactoredBackupManagerService;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportUtils;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
@@ -112,7 +114,6 @@ public class PerformBackupTask implements BackupRestoreTask {
private RefactoredBackupManagerService backupManagerService;
private final Object mCancelLock = new Object();
- IBackupTransport mTransport;
ArrayList<BackupRequest> mQueue;
ArrayList<BackupRequest> mOriginalQueue;
File mStateDir;
@@ -122,6 +123,8 @@ public class PerformBackupTask implements BackupRestoreTask {
IBackupObserver mObserver;
IBackupManagerMonitor mMonitor;
+ private final TransportClient mTransportClient;
+ private final OnTaskFinishedListener mListener;
private final PerformFullTransportBackupTask mFullBackupTask;
private final int mCurrentOpToken;
private volatile int mEphemeralOpToken;
@@ -143,17 +146,19 @@ public class PerformBackupTask implements BackupRestoreTask {
private volatile boolean mCancelAll;
public PerformBackupTask(RefactoredBackupManagerService backupManagerService,
- IBackupTransport transport, String dirName,
+ TransportClient transportClient, String dirName,
ArrayList<BackupRequest> queue, @Nullable DataChangedJournal journal,
IBackupObserver observer, IBackupManagerMonitor monitor,
- List<String> pendingFullBackups, boolean userInitiated, boolean nonIncremental) {
+ @Nullable OnTaskFinishedListener listener, List<String> pendingFullBackups,
+ boolean userInitiated, boolean nonIncremental) {
this.backupManagerService = backupManagerService;
- mTransport = transport;
+ mTransportClient = transportClient;
mOriginalQueue = queue;
mQueue = new ArrayList<>();
mJournal = journal;
mObserver = observer;
mMonitor = monitor;
+ mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP;
mPendingFullBackups = pendingFullBackups;
mUserInitiated = userInitiated;
mNonIncremental = nonIncremental;
@@ -179,11 +184,11 @@ public class PerformBackupTask implements BackupRestoreTask {
mPendingFullBackups.toArray(new String[mPendingFullBackups.size()]);
mFullBackupTask =
new PerformFullTransportBackupTask(backupManagerService,
- /*fullBackupRestoreObserver*/
- null,
+ transportClient,
+ /*fullBackupRestoreObserver*/ null,
fullBackups, /*updateSchedule*/ false, /*runningJob*/ null,
latch,
- mObserver, mMonitor, mUserInitiated);
+ mObserver, mMonitor, mListener, mUserInitiated);
registerTask();
backupManagerService.addBackupTrace("STATE => INITIAL");
@@ -289,10 +294,10 @@ public class PerformBackupTask implements BackupRestoreTask {
if (DEBUG) {
Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
}
-
File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
try {
- final String transportName = mTransport.transportDirName();
+ IBackupTransport transport = mTransportClient.connectOrThrow("PBT.beginBackup()");
+ final String transportName = transport.transportDirName();
EventLog.writeEvent(EventLogTags.BACKUP_START, transportName);
// If we haven't stored package manager metadata yet, we must init the transport.
@@ -300,7 +305,7 @@ public class PerformBackupTask implements BackupRestoreTask {
Slog.i(TAG, "Initializing (wiping) backup state and transport storage");
backupManagerService.addBackupTrace("initializing transport " + transportName);
backupManagerService.resetBackupState(mStateDir); // Just to make sure.
- mStatus = mTransport.initializeDevice();
+ mStatus = transport.initializeDevice();
backupManagerService.addBackupTrace("transport.initializeDevice() == " + mStatus);
if (mStatus == BackupTransport.TRANSPORT_OK) {
@@ -324,7 +329,7 @@ public class PerformBackupTask implements BackupRestoreTask {
PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent();
mStatus = invokeAgentForBackup(
PACKAGE_MANAGER_SENTINEL,
- IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
+ IBackupAgent.Stub.asInterface(pmAgent.onBind()));
backupManagerService.addBackupTrace("PMBA invoke: " + mStatus);
// Because the PMBA is a local instance, it has already executed its
@@ -445,7 +450,7 @@ public class PerformBackupTask implements BackupRestoreTask {
backupManagerService.addBackupTrace("agent bound; a? = " + (agent != null));
if (agent != null) {
mAgentBinder = agent;
- mStatus = invokeAgentForBackup(request.packageName, agent, mTransport);
+ mStatus = invokeAgentForBackup(request.packageName, agent);
// at this point we'll either get a completion callback from the
// agent, or a timeout message on the main handler. either way, we're
// done here as long as we're successful so far.
@@ -526,11 +531,14 @@ public class PerformBackupTask implements BackupRestoreTask {
// If everything actually went through and this is the first time we've
// done a backup, we can now record what the current backup dataset token
// is.
+ String callerLogString = "PBT.finalizeBackup()";
if ((backupManagerService.getCurrentToken() == 0) && (mStatus
== BackupTransport.TRANSPORT_OK)) {
backupManagerService.addBackupTrace("success; recording token");
try {
- backupManagerService.setCurrentToken(mTransport.getCurrentRestoreSet());
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow(callerLogString);
+ backupManagerService.setCurrentToken(transport.getCurrentRestoreSet());
backupManagerService.writeRestoreTokens();
} catch (Exception e) {
// nothing for it at this point, unfortunately, but this will be
@@ -553,13 +561,13 @@ public class PerformBackupTask implements BackupRestoreTask {
backupManagerService.addBackupTrace("init required; rerunning");
try {
final String name = backupManagerService.getTransportManager().getTransportName(
- mTransport);
+ mTransportClient);
if (name != null) {
backupManagerService.getPendingInits().add(name);
} else {
if (DEBUG) {
- Slog.w(TAG, "Couldn't find name of transport " + mTransport
- + " for init");
+ Slog.w(TAG, "Couldn't find name of transport "
+ + mTransportClient.getTransportComponent() + " for init");
}
}
} catch (Exception e) {
@@ -580,14 +588,18 @@ public class PerformBackupTask implements BackupRestoreTask {
Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
// Acquiring wakelock for PerformFullTransportBackupTask before its start.
backupManagerService.getWakelock().acquire();
+ // The full-backup task is now responsible for calling onFinish() on mListener, which
+ // was the listener we passed it.
(new Thread(mFullBackupTask, "full-transport-requested")).start();
} else if (mCancelAll) {
+ mListener.onFinished(callerLogString);
if (mFullBackupTask != null) {
mFullBackupTask.unregisterTask();
}
BackupObserverUtils.sendBackupFinished(mObserver,
BackupManager.ERROR_BACKUP_CANCELLED);
} else {
+ mListener.onFinished(callerLogString);
mFullBackupTask.unregisterTask();
switch (mStatus) {
case BackupTransport.TRANSPORT_OK:
@@ -619,8 +631,7 @@ public class PerformBackupTask implements BackupRestoreTask {
// Invoke an agent's doBackup() and start a timeout message spinning on the main
// handler in case it doesn't get back to us.
- int invokeAgentForBackup(String packageName, IBackupAgent agent,
- IBackupTransport transport) {
+ int invokeAgentForBackup(String packageName, IBackupAgent agent) {
if (DEBUG) {
Slog.d(TAG, "invokeAgentForBackup on " + packageName);
}
@@ -671,7 +682,10 @@ public class PerformBackupTask implements BackupRestoreTask {
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
- final long quota = mTransport.getBackupQuota(packageName, false /* isFullBackup */);
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow("PBT.invokeAgentForBackup()");
+
+ final long quota = transport.getBackupQuota(packageName, false /* isFullBackup */);
callingAgent = true;
// Initiate the target's backup pass
@@ -888,10 +902,12 @@ public class PerformBackupTask implements BackupRestoreTask {
clearAgentState();
backupManagerService.addBackupTrace("operation complete");
+ IBackupTransport transport = mTransportClient.connect("PBT.operationComplete()");
ParcelFileDescriptor backupData = null;
mStatus = BackupTransport.TRANSPORT_OK;
long size = 0;
try {
+ TransportUtils.checkTransport(transport);
size = mBackupDataName.length();
if (size > 0) {
if (mStatus == BackupTransport.TRANSPORT_OK) {
@@ -899,7 +915,7 @@ public class PerformBackupTask implements BackupRestoreTask {
ParcelFileDescriptor.MODE_READ_ONLY);
backupManagerService.addBackupTrace("sending data to transport");
int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
- mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
+ mStatus = transport.performBackup(mCurrentPackage, backupData, flags);
}
// TODO - We call finishBackup() for each application backed up, because
@@ -910,7 +926,7 @@ public class PerformBackupTask implements BackupRestoreTask {
backupManagerService.addBackupTrace("data delivered: " + mStatus);
if (mStatus == BackupTransport.TRANSPORT_OK) {
backupManagerService.addBackupTrace("finishing op on transport");
- mStatus = mTransport.finishBackup();
+ mStatus = transport.finishBackup();
backupManagerService.addBackupTrace("finished: " + mStatus);
} else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
backupManagerService.addBackupTrace("transport rejected package");
@@ -981,8 +997,8 @@ public class PerformBackupTask implements BackupRestoreTask {
}
if (mAgentBinder != null) {
try {
- long quota = mTransport.getBackupQuota(mCurrentPackage.packageName,
- false);
+ TransportUtils.checkTransport(transport);
+ long quota = transport.getBackupQuota(mCurrentPackage.packageName, false);
mAgentBinder.doQuotaExceeded(size, quota);
} catch (Exception e) {
Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
@@ -1052,7 +1068,9 @@ public class PerformBackupTask implements BackupRestoreTask {
// by way of retry/backoff time.
long delay;
try {
- delay = mTransport.requestBackupTime();
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow("PBT.revertAndEndBackup()");
+ delay = transport.requestBackupTime();
} catch (Exception e) {
Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e.getMessage());
delay = 0; // use the scheduler's default
diff --git a/com/android/server/backup/params/BackupParams.java b/com/android/server/backup/params/BackupParams.java
index 4fd7ddbc..2ba8ec16 100644
--- a/com/android/server/backup/params/BackupParams.java
+++ b/com/android/server/backup/params/BackupParams.java
@@ -19,30 +19,34 @@ package com.android.server.backup.params;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
-import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.transport.TransportClient;
import java.util.ArrayList;
public class BackupParams {
- public IBackupTransport transport;
+ public TransportClient transportClient;
public String dirName;
public ArrayList<String> kvPackages;
public ArrayList<String> fullPackages;
public IBackupObserver observer;
public IBackupManagerMonitor monitor;
+ public OnTaskFinishedListener listener;
public boolean userInitiated;
public boolean nonIncrementalBackup;
- public BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages,
- ArrayList<String> fullPackages, IBackupObserver observer,
- IBackupManagerMonitor monitor, boolean userInitiated, boolean nonIncrementalBackup) {
- this.transport = transport;
+ public BackupParams(TransportClient transportClient, String dirName,
+ ArrayList<String> kvPackages, ArrayList<String> fullPackages, IBackupObserver observer,
+ IBackupManagerMonitor monitor, OnTaskFinishedListener listener, boolean userInitiated,
+ boolean nonIncrementalBackup) {
+ this.transportClient = transportClient;
this.dirName = dirName;
this.kvPackages = kvPackages;
this.fullPackages = fullPackages;
this.observer = observer;
this.monitor = monitor;
+ this.listener = listener;
this.userInitiated = userInitiated;
this.nonIncrementalBackup = nonIncrementalBackup;
}
diff --git a/com/android/server/backup/transport/TransportClient.java b/com/android/server/backup/transport/TransportClient.java
new file mode 100644
index 00000000..65f95022
--- /dev/null
+++ b/com/android/server/backup/transport/TransportClient.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.transport;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.backup.IBackupTransport;
+import com.android.internal.util.Preconditions;
+import com.android.server.backup.TransportManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * A {@link TransportClient} manages the connection to an {@link IBackupTransport} service, obtained
+ * via the {@param bindIntent} parameter provided in the constructor. A {@link TransportClient} is
+ * responsible for only one connection to the transport service, not more.
+ *
+ * <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can
+ * call either {@link #connect(String)}, if you can block your thread, or {@link
+ * #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link
+ * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport.
+ * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly
+ * via {@link TransportManager#disposeOfTransportClient(TransportClient, String)}.
+ *
+ * <p>DO NOT forget to unbind otherwise there will be dangling connections floating around.
+ *
+ * <p>This class is thread-safe.
+ *
+ * @see TransportManager
+ */
+public class TransportClient {
+ private static final String TAG = "TransportClient";
+
+ private final Context mContext;
+ private final Intent mBindIntent;
+ private final String mIdentifier;
+ private final ComponentName mTransportComponent;
+ private final Handler mListenerHandler;
+ private final String mPrefixForLog;
+ private final Object mStateLock = new Object();
+
+ @GuardedBy("mStateLock")
+ private final Map<TransportConnectionListener, String> mListeners = new ArrayMap<>();
+
+ @GuardedBy("mStateLock")
+ @State
+ private int mState = State.IDLE;
+
+ @GuardedBy("mStateLock")
+ private volatile IBackupTransport mTransport;
+
+ TransportClient(
+ Context context,
+ Intent bindIntent,
+ ComponentName transportComponent,
+ String identifier) {
+ this(context, bindIntent, transportComponent, identifier, Handler.getMain());
+ }
+
+ @VisibleForTesting
+ TransportClient(
+ Context context,
+ Intent bindIntent,
+ ComponentName transportComponent,
+ String identifier,
+ Handler listenerHandler) {
+ mContext = context;
+ mTransportComponent = transportComponent;
+ mBindIntent = bindIntent;
+ mIdentifier = identifier;
+ mListenerHandler = listenerHandler;
+
+ // For logging
+ String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", "");
+ mPrefixForLog = classNameForLog + "#" + mIdentifier + ": ";
+ }
+
+ public ComponentName getTransportComponent() {
+ return mTransportComponent;
+ }
+
+ // Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one
+ // of these calls, if a binding happen again the new service can be a different instance. Since
+ // transports are stateful, we don't want a new instance responding for an old instance's state.
+ private ServiceConnection mConnection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder binder) {
+ IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ if (mState != State.UNUSABLE) {
+ log(Log.DEBUG, "Transport connected");
+ setStateLocked(State.CONNECTED, transport);
+ notifyListenersAndClearLocked(transport);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ synchronized (mStateLock) {
+ log(Log.ERROR, "Service disconnected: client UNUSABLE");
+ setStateLocked(State.UNUSABLE, null);
+ // After unbindService() no calls back to mConnection
+ mContext.unbindService(this);
+ }
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ log(Log.ERROR, "Binding died: client UNUSABLE");
+ // After unbindService() no calls back to mConnection
+ switch (mState) {
+ case State.UNUSABLE:
+ break;
+ case State.IDLE:
+ log(Log.ERROR, "Unexpected state transition IDLE => UNUSABLE");
+ setStateLocked(State.UNUSABLE, null);
+ break;
+ case State.BOUND_AND_CONNECTING:
+ setStateLocked(State.UNUSABLE, null);
+ mContext.unbindService(this);
+ notifyListenersAndClearLocked(null);
+ break;
+ case State.CONNECTED:
+ setStateLocked(State.UNUSABLE, null);
+ mContext.unbindService(this);
+ break;
+ }
+ }
+ }
+ };
+
+ /**
+ * Attempts to connect to the transport (if needed).
+ *
+ * <p>Note that being bound is not the same as connected. To be connected you also need to be
+ * bound. You go from nothing to bound, then to bound and connected. To have a usable transport
+ * binder instance you need to be connected. This method will attempt to connect and return an
+ * usable transport binder regardless of the state of the object, it may already be connected,
+ * or bound but not connected, not bound at all or even unusable.
+ *
+ * <p>So, a {@link Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)} (or
+ * one of its variants) can be called or not depending on the inner state. However, it won't be
+ * called again if we're already bound. For example, if one was already requested but the
+ * framework has not yet returned (meaning we're bound but still trying to connect) it won't
+ * trigger another one, just piggyback on the original request.
+ *
+ * <p>It's guaranteed that you are going to get a call back to {@param listener} after this
+ * call. However, the {@param IBackupTransport} parameter, the transport binder, is not
+ * guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can
+ * throw {@link DeadObjectException}s on method calls. You should check for both in your code.
+ * The reasons for a null transport binder are:
+ *
+ * <ul>
+ * <li>Some code called {@link #unbind(String)} before you got a callback.
+ * <li>The framework had already called {@link
+ * ServiceConnection#onServiceDisconnected(ComponentName)} or {@link
+ * ServiceConnection#onBindingDied(ComponentName)} on this object's connection before.
+ * Check the documentation of those methods for when that happens.
+ * <li>The framework returns false for {@link Context#bindServiceAsUser(Intent,
+ * ServiceConnection, int, UserHandle)} (or one of its variants). Check documentation for
+ * when this happens.
+ * </ul>
+ *
+ * For unusable transport binders check {@link DeadObjectException}.
+ *
+ * @param listener The listener that will be called with the (possibly null or unusable) {@link
+ * IBackupTransport} instance and this {@link TransportClient} object.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. This
+ * should be a human-readable short string that is easily identifiable in the logs. Ideally
+ * TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very
+ * descriptive like MyHandler.handleMessage() you should put something that someone reading
+ * the code would understand, like MyHandler/MSG_FOO.
+ * @see #connect(String)
+ * @see DeadObjectException
+ * @see ServiceConnection#onServiceConnected(ComponentName, IBinder)
+ * @see ServiceConnection#onServiceDisconnected(ComponentName)
+ * @see Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)
+ */
+ public void connectAsync(TransportConnectionListener listener, String caller) {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ switch (mState) {
+ case State.UNUSABLE:
+ log(Log.DEBUG, caller, "Async connect: UNUSABLE client");
+ notifyListener(listener, null, caller);
+ break;
+ case State.IDLE:
+ boolean hasBound =
+ mContext.bindServiceAsUser(
+ mBindIntent,
+ mConnection,
+ Context.BIND_AUTO_CREATE,
+ TransportManager.createSystemUserHandle());
+ if (hasBound) {
+ // We don't need to set a time-out because we are guaranteed to get a call
+ // back in ServiceConnection, either an onServiceConnected() or
+ // onBindingDied().
+ log(Log.DEBUG, caller, "Async connect: service bound, connecting");
+ setStateLocked(State.BOUND_AND_CONNECTING, null);
+ mListeners.put(listener, caller);
+ } else {
+ log(Log.ERROR, "Async connect: bindService returned false");
+ // mState remains State.IDLE
+ mContext.unbindService(mConnection);
+ notifyListener(listener, null, caller);
+ }
+ break;
+ case State.BOUND_AND_CONNECTING:
+ log(Log.DEBUG, caller, "Async connect: already connecting, adding listener");
+ mListeners.put(listener, caller);
+ break;
+ case State.CONNECTED:
+ log(Log.DEBUG, caller, "Async connect: reusing transport");
+ notifyListener(listener, mTransport, caller);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Removes the transport binding.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ */
+ public void unbind(String caller) {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ log(Log.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")");
+ switch (mState) {
+ case State.UNUSABLE:
+ case State.IDLE:
+ break;
+ case State.BOUND_AND_CONNECTING:
+ setStateLocked(State.IDLE, null);
+ // After unbindService() no calls back to mConnection
+ mContext.unbindService(mConnection);
+ notifyListenersAndClearLocked(null);
+ break;
+ case State.CONNECTED:
+ setStateLocked(State.IDLE, null);
+ mContext.unbindService(mConnection);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Attempts to connect to the transport (if needed) and returns it.
+ *
+ * <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The
+ * same observations about state are valid here. Also, what was said about the {@link
+ * IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return
+ * value of this method.
+ *
+ * <p>This is a potentially blocking operation, so be sure to call this carefully on the correct
+ * threads. You can't call this from the process main-thread (it throws an exception if you do
+ * so).
+ *
+ * <p>In most cases only the first call to this method will block, the following calls should
+ * return instantly. However, this is not guaranteed.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ * @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can
+ * still be unusable - throws {@link DeadObjectException} on method calls
+ */
+ @WorkerThread
+ @Nullable
+ public IBackupTransport connect(String caller) {
+ // If called on the main-thread this could deadlock waiting because calls to
+ // ServiceConnection are on the main-thread as well
+ Preconditions.checkState(
+ !Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread");
+
+ IBackupTransport transport = mTransport;
+ if (transport != null) {
+ log(Log.DEBUG, caller, "Sync connect: reusing transport");
+ return transport;
+ }
+
+ // If it's already UNUSABLE we return straight away, no need to go to main-thread
+ synchronized (mStateLock) {
+ if (mState == State.UNUSABLE) {
+ log(Log.DEBUG, caller, "Sync connect: UNUSABLE client");
+ return null;
+ }
+ }
+
+ CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>();
+ TransportConnectionListener requestListener =
+ (requestedTransport, transportClient) ->
+ transportFuture.complete(requestedTransport);
+
+ log(Log.DEBUG, caller, "Sync connect: calling async");
+ connectAsync(requestListener, caller);
+
+ try {
+ return transportFuture.get();
+ } catch (InterruptedException | ExecutionException e) {
+ String error = e.getClass().getSimpleName();
+ log(Log.ERROR, caller, error + " while waiting for transport: " + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Tries to connect to the transport, if it fails throws {@link TransportNotAvailableException}.
+ *
+ * <p>Same as {@link #connect(String)} except it throws instead of returning null.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ * @return A {@link IBackupTransport} transport binder instance.
+ * @see #connect(String)
+ * @throws TransportNotAvailableException if connection attempt fails.
+ */
+ @WorkerThread
+ public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException {
+ IBackupTransport transport = connect(caller);
+ if (transport == null) {
+ log(Log.ERROR, caller, "Transport connection failed");
+ throw new TransportNotAvailableException();
+ }
+ return transport;
+ }
+
+ /**
+ * If the {@link TransportClient} is already connected to the transport, returns the transport,
+ * otherwise throws {@link TransportNotAvailableException}.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ * @return A {@link IBackupTransport} transport binder instance.
+ * @throws TransportNotAvailableException if not connected.
+ */
+ public IBackupTransport getConnectedTransport(String caller)
+ throws TransportNotAvailableException {
+ IBackupTransport transport = mTransport;
+ if (transport == null) {
+ log(Log.ERROR, caller, "Transport not connected");
+ throw new TransportNotAvailableException();
+ }
+ return transport;
+ }
+
+ @Override
+ public String toString() {
+ return "TransportClient{"
+ + mTransportComponent.flattenToShortString()
+ + "#"
+ + mIdentifier
+ + "}";
+ }
+
+ private void notifyListener(
+ TransportConnectionListener listener, IBackupTransport transport, String caller) {
+ log(Log.VERBOSE, caller, "Notifying listener of transport = " + transport);
+ mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this));
+ }
+
+ @GuardedBy("mStateLock")
+ private void notifyListenersAndClearLocked(IBackupTransport transport) {
+ for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) {
+ TransportConnectionListener listener = entry.getKey();
+ String caller = entry.getValue();
+ notifyListener(listener, transport, caller);
+ }
+ mListeners.clear();
+ }
+
+ @GuardedBy("mStateLock")
+ private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
+ log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
+ mState = state;
+ mTransport = transport;
+ }
+
+ @GuardedBy("mStateLock")
+ private void checkStateIntegrityLocked() {
+ switch (mState) {
+ case State.UNUSABLE:
+ checkState(mListeners.isEmpty(), "Unexpected listeners when state = UNUSABLE");
+ checkState(
+ mTransport == null, "Transport expected to be null when state = UNUSABLE");
+ case State.IDLE:
+ checkState(mListeners.isEmpty(), "Unexpected listeners when state = IDLE");
+ checkState(mTransport == null, "Transport expected to be null when state = IDLE");
+ break;
+ case State.BOUND_AND_CONNECTING:
+ checkState(
+ mTransport == null,
+ "Transport expected to be null when state = BOUND_AND_CONNECTING");
+ break;
+ case State.CONNECTED:
+ checkState(mListeners.isEmpty(), "Unexpected listeners when state = CONNECTED");
+ checkState(
+ mTransport != null,
+ "Transport expected to be non-null when state = CONNECTED");
+ break;
+ default:
+ checkState(false, "Unexpected state = " + stateToString(mState));
+ }
+ }
+
+ private void checkState(boolean assertion, String message) {
+ if (!assertion) {
+ log(Log.ERROR, message);
+ }
+ }
+
+ private String stateToString(@State int state) {
+ switch (state) {
+ case State.UNUSABLE:
+ return "UNUSABLE";
+ case State.IDLE:
+ return "IDLE";
+ case State.BOUND_AND_CONNECTING:
+ return "BOUND_AND_CONNECTING";
+ case State.CONNECTED:
+ return "CONNECTED";
+ default:
+ return "<UNKNOWN = " + state + ">";
+ }
+ }
+
+ private void log(int priority, String message) {
+ TransportUtils.log(priority, TAG, message);
+ }
+
+ private void log(int priority, String caller, String msg) {
+ TransportUtils.log(priority, TAG, mPrefixForLog, caller, msg);
+ // TODO(brufino): Log in internal list for dump
+ // CharSequence time = DateFormat.format("yyyy-MM-dd HH:mm:ss", System.currentTimeMillis());
+ }
+
+ @IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface State {
+ int UNUSABLE = 0;
+ int IDLE = 1;
+ int BOUND_AND_CONNECTING = 2;
+ int CONNECTED = 3;
+ }
+}
diff --git a/com/android/server/backup/transport/TransportClientManager.java b/com/android/server/backup/transport/TransportClientManager.java
new file mode 100644
index 00000000..1cbe7471
--- /dev/null
+++ b/com/android/server/backup/transport/TransportClientManager.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.transport;
+
+import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.server.backup.TransportManager;
+
+/**
+ * Manages the creation and disposal of {@link TransportClient}s. The only class that should use
+ * this is {@link TransportManager}, all the other usages should go to {@link TransportManager}.
+ *
+ * <p>TODO(brufino): Implement pool of TransportClients
+ */
+public class TransportClientManager {
+ private static final String TAG = "TransportClientManager";
+
+ private final Context mContext;
+ private final Object mTransportClientsLock = new Object();
+ private int mTransportClientsCreated = 0;
+
+ public TransportClientManager(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Retrieves a {@link TransportClient} for the transport identified by {@param
+ * transportComponent}.
+ *
+ * @param transportComponent The {@link ComponentName} of the transport.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient}.
+ */
+ public TransportClient getTransportClient(ComponentName transportComponent, String caller) {
+ Intent bindIntent =
+ new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
+ synchronized (mTransportClientsLock) {
+ TransportClient transportClient =
+ new TransportClient(
+ mContext,
+ bindIntent,
+ transportComponent,
+ Integer.toString(mTransportClientsCreated));
+ mTransportClientsCreated++;
+ TransportUtils.log(Log.DEBUG, TAG, caller, "Retrieving " + transportClient);
+ return transportClient;
+ }
+ }
+
+ /**
+ * Disposes of the {@link TransportClient}.
+ *
+ * @param transportClient The {@link TransportClient} to be disposed of.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ public void disposeOfTransportClient(TransportClient transportClient, String caller) {
+ TransportUtils.log(Log.DEBUG, TAG, caller, "Disposing of " + transportClient);
+ transportClient.unbind(caller);
+ }
+}
diff --git a/com/android/server/backup/transport/TransportClientTest.java b/com/android/server/backup/transport/TransportClientTest.java
new file mode 100644
index 00000000..54d233a9
--- /dev/null
+++ b/com/android/server/backup/transport/TransportClientTest.java
@@ -0,0 +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 com.android.server.backup.transport;
+
+import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.backup.IBackupTransport;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 23)
+@Presubmit
+public class TransportClientTest {
+ private static final String PACKAGE_NAME = "some.package.name";
+ private static final ComponentName TRANSPORT_COMPONENT =
+ new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport");
+
+ @Mock private Context mContext;
+ @Mock private TransportConnectionListener mTransportConnectionListener;
+ @Mock private TransportConnectionListener mTransportConnectionListener2;
+ @Mock private IBackupTransport.Stub mIBackupTransport;
+ private TransportClient mTransportClient;
+ private Intent mBindIntent;
+ private ShadowLooper mShadowLooper;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ Looper mainLooper = Looper.getMainLooper();
+ mShadowLooper = shadowOf(mainLooper);
+ mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(TRANSPORT_COMPONENT);
+ mTransportClient =
+ new TransportClient(
+ mContext, mBindIntent, TRANSPORT_COMPONENT, "1", new Handler(mainLooper));
+
+ when(mContext.bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class)))
+ .thenReturn(true);
+ }
+
+ // TODO: Testing implementation? Remove?
+ @Test
+ public void testConnectAsync_callsBindService() throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ verify(mContext)
+ .bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class));
+ }
+
+ @Test
+ public void testConnectAsync_callsListenerWhenConnected() throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ // Simulate framework connecting
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenPendingConnection_callsAllListenersWhenConnected()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
+
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenAlreadyConnected_callsListener() throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenFrameworkDoesntBind_callsListener() throws Exception {
+ when(mContext.bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class)))
+ .thenReturn(false);
+
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenFrameworkDoesntBind_releasesConnection() throws Exception {
+ when(mContext.bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class)))
+ .thenReturn(false);
+
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ verify(mContext).unbindService(eq(connection));
+ }
+
+ @Test
+ public void testConnectAsync_afterServiceDisconnectedBeforeNewConnection_callsListener()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+ connection.onServiceDisconnected(TRANSPORT_COMPONENT);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
+
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_afterServiceDisconnectedAfterNewConnection_callsListener()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+ connection.onServiceDisconnected(TRANSPORT_COMPONENT);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
+
+ // Yes, it should return null because the object became unusable, check design doc
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
+
+ // TODO(b/69153972): Support SDK 26 API (ServiceConnection.inBindingDied) for transport tests
+ /*@Test
+ public void testConnectAsync_callsListenerIfBindingDies() throws Exception {
+ mTransportClient.connectAsync(mTransportListener, "caller");
+
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onBindingDied(TRANSPORT_COMPONENT);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+
+ mTransportClient.connectAsync(mTransportListener2, "caller2");
+
+ connection.onBindingDied(TRANSPORT_COMPONENT);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
+ verify(mTransportListener2).onTransportBound(isNull(), eq(mTransportClient));
+ }*/
+
+ private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) {
+ ArgumentCaptor<ServiceConnection> connectionCaptor =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ verify(context)
+ .bindServiceAsUser(
+ any(Intent.class),
+ connectionCaptor.capture(),
+ anyInt(),
+ any(UserHandle.class));
+ return connectionCaptor.getValue();
+ }
+}
diff --git a/com/android/server/backup/transport/TransportConnectionListener.java b/com/android/server/backup/transport/TransportConnectionListener.java
new file mode 100644
index 00000000..1ccffd01
--- /dev/null
+++ b/com/android/server/backup/transport/TransportConnectionListener.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 com.android.server.backup.transport;
+
+import android.annotation.Nullable;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Listener to be called by {@link TransportClient#connectAsync(TransportConnectionListener,
+ * String)}.
+ */
+public interface TransportConnectionListener {
+ /**
+ * Called when {@link TransportClient} has a transport binder available or that it decided it
+ * couldn't obtain one, in which case {@param transport} is null.
+ *
+ * @param transport A {@link IBackupTransport} transport binder or null.
+ * @param transportClient The {@link TransportClient} used to retrieve this transport binder.
+ */
+ void onTransportConnectionResult(
+ @Nullable IBackupTransport transport, TransportClient transportClient);
+}
diff --git a/com/android/server/backup/transport/TransportNotAvailableException.java b/com/android/server/backup/transport/TransportNotAvailableException.java
new file mode 100644
index 00000000..c4e5a1dd
--- /dev/null
+++ b/com/android/server/backup/transport/TransportNotAvailableException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.transport;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Exception thrown when the {@link IBackupTransport} is not available. This happen when a {@link
+ * TransportClient} connection attempt fails. Check {@link
+ * TransportClient#connectAsync(TransportConnectionListener, String)} for when that happens.
+ *
+ * @see TransportClient#connectAsync(TransportConnectionListener, String)
+ */
+public class TransportNotAvailableException extends Exception {
+ TransportNotAvailableException() {
+ super("Transport not available");
+ }
+}
diff --git a/com/android/server/backup/transport/TransportUtils.java b/com/android/server/backup/transport/TransportUtils.java
new file mode 100644
index 00000000..85599b78
--- /dev/null
+++ b/com/android/server/backup/transport/TransportUtils.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.transport;
+
+import android.annotation.Nullable;
+import android.os.DeadObjectException;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.backup.IBackupTransport;
+
+/** Utility methods for transport-related operations. */
+public class TransportUtils {
+ private static final String TAG = "TransportUtils";
+
+ /**
+ * Throws {@link TransportNotAvailableException} if {@param transport} is null. The semantics is
+ * similar to a {@link DeadObjectException} coming from a dead transport binder.
+ */
+ public static IBackupTransport checkTransport(@Nullable IBackupTransport transport)
+ throws TransportNotAvailableException {
+ if (transport == null) {
+ log(Log.ERROR, TAG, "Transport not available");
+ throw new TransportNotAvailableException();
+ }
+ return transport;
+ }
+
+ static void log(int priority, String tag, String message) {
+ log(priority, tag, null, message);
+ }
+
+ static void log(int priority, String tag, @Nullable String caller, String message) {
+ log(priority, tag, "", caller, message);
+ }
+
+ static void log(
+ int priority, String tag, String prefix, @Nullable String caller, String message) {
+ if (Log.isLoggable(tag, priority)) {
+ if (caller != null) {
+ prefix += "[" + caller + "] ";
+ }
+ Slog.println(priority, tag, prefix + message);
+ }
+ }
+
+ private TransportUtils() {}
+}
diff --git a/com/android/server/broadcastradio/Tuner.java b/com/android/server/broadcastradio/Tuner.java
index e6ae320c..2ea42718 100644
--- a/com/android/server/broadcastradio/Tuner.java
+++ b/com/android/server/broadcastradio/Tuner.java
@@ -27,8 +27,10 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
class Tuner extends ITuner.Stub {
private static final String TAG = "BroadcastRadioService.Tuner";
@@ -96,6 +98,10 @@ class Tuner extends ITuner.Stub {
private native boolean nativeIsAnalogForced(long nativeContext);
private native void nativeSetAnalogForced(long nativeContext, boolean isForced);
+ private native Map<String, String> nativeSetParameters(long nativeContext,
+ Map<String, String> parameters);
+ private native Map<String, String> nativeGetParameters(long nativeContext, List<String> keys);
+
private native boolean nativeIsAntennaConnected(long nativeContext);
@Override
@@ -273,6 +279,31 @@ class Tuner extends ITuner.Stub {
}
@Override
+ public Map setParameters(Map parameters) {
+ Map<String, String> results;
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ results = nativeSetParameters(mNativeContext, Objects.requireNonNull(parameters));
+ }
+ if (results == null) return Collections.emptyMap();
+ return results;
+ }
+
+ @Override
+ public Map getParameters(List<String> keys) {
+ if (keys == null) {
+ throw new IllegalArgumentException("The argument must not be a null pointer");
+ }
+ Map<String, String> results;
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ results = nativeGetParameters(mNativeContext, keys);
+ }
+ if (results == null) return Collections.emptyMap();
+ return results;
+ }
+
+ @Override
public boolean isAntennaConnected() {
synchronized (mLock) {
checkNotClosedLocked();
diff --git a/com/android/server/broadcastradio/TunerCallback.java b/com/android/server/broadcastradio/TunerCallback.java
index a87ae8d6..2460c67a 100644
--- a/com/android/server/broadcastradio/TunerCallback.java
+++ b/com/android/server/broadcastradio/TunerCallback.java
@@ -26,6 +26,9 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import java.util.List;
+import java.util.Map;
+
class TunerCallback implements ITunerCallback {
private static final String TAG = "BroadcastRadioService.TunerCallback";
@@ -121,6 +124,11 @@ class TunerCallback implements ITunerCallback {
}
@Override
+ public void onParametersUpdated(Map parameters) {
+ dispatch(() -> mClientCallback.onParametersUpdated(parameters));
+ }
+
+ @Override
public IBinder asBinder() {
throw new RuntimeException("Not a binder");
}
diff --git a/com/android/server/connectivity/NetdEventListenerService.java b/com/android/server/connectivity/NetdEventListenerService.java
index 4bdbbe39..e243e56c 100644
--- a/com/android/server/connectivity/NetdEventListenerService.java
+++ b/com/android/server/connectivity/NetdEventListenerService.java
@@ -21,6 +21,7 @@ import static android.util.TimeUtils.NANOS_PER_MS;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.INetdEventCallback;
+import android.net.MacAddress;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.metrics.ConnectStats;
@@ -35,6 +36,7 @@ import android.text.format.DateUtils;
import android.util.Log;
import android.util.ArrayMap;
import android.util.SparseArray;
+import android.util.StatsLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -242,13 +244,17 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
event.timestampMs = timestampMs;
event.uid = uid;
event.ethertype = ethertype;
- event.dstHwAddr = dstHw;
+ event.dstHwAddr = new MacAddress(dstHw);
event.srcIp = srcIp;
event.dstIp = dstIp;
event.ipNextHeader = ipNextHeader;
event.srcPort = srcPort;
event.dstPort = dstPort;
addWakeupEvent(event);
+
+ String dstMac = event.dstHwAddr.toString();
+ StatsLog.write(StatsLog.PACKET_WAKEUP_OCCURRED,
+ uid, iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort);
}
private void addWakeupEvent(WakeupEvent event) {
diff --git a/com/android/server/devicepolicy/DevicePolicyManagerService.java b/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2d8a0ee0..60c36d1b 100644
--- a/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4917,7 +4917,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean installKeyPair(ComponentName who, String callerPackage, byte[] privKey,
- byte[] cert, byte[] chain, String alias, boolean requestAccess) {
+ byte[] cert, byte[] chain, String alias, boolean requestAccess,
+ boolean isUserSelectable) {
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
DELEGATION_CERT_INSTALL);
@@ -4935,6 +4936,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (requestAccess) {
keyChain.setGrant(callingUid, alias, true);
}
+ keyChain.setUserSelectable(alias, isUserSelectable);
return true;
} catch (RemoteException e) {
Log.e(LOG_TAG, "Installing certificate", e);
diff --git a/com/android/server/devicepolicy/NetworkLoggingHandler.java b/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 60863549..4a6bee5c 100644
--- a/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -64,6 +64,8 @@ final class NetworkLoggingHandler extends Handler {
private final DevicePolicyManagerService mDpm;
private final AlarmManager mAlarmManager;
+ private long mId;
+
private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() {
@Override
public void onAlarm() {
@@ -185,6 +187,10 @@ final class NetworkLoggingHandler extends Handler {
private Bundle finalizeBatchAndBuildDeviceOwnerMessageLocked() {
Bundle notificationExtras = null;
if (mNetworkEvents.size() > 0) {
+ // Assign ids to the events.
+ for (NetworkEvent event : mNetworkEvents) {
+ event.setId(mId++);
+ }
// Finalize the batch and start a new one from scratch.
if (mBatches.size() >= MAX_BATCHES) {
// Remove the oldest batch if we hit the limit.
diff --git a/com/android/server/display/BrightnessIdleJob.java b/com/android/server/display/BrightnessIdleJob.java
new file mode 100644
index 00000000..876acf45
--- /dev/null
+++ b/com/android/server/display/BrightnessIdleJob.java
@@ -0,0 +1,81 @@
+/*
+ * 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.display;
+
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * JobService used to persists brightness slider events when the device
+ * is idle and charging.
+ */
+public class BrightnessIdleJob extends JobService {
+
+ // Must be unique within the system server uid.
+ private static final int JOB_ID = 3923512;
+
+ public static void scheduleJob(Context context) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+
+ JobInfo pending = jobScheduler.getPendingJob(JOB_ID);
+ JobInfo jobInfo =
+ new JobInfo.Builder(JOB_ID, new ComponentName(context, BrightnessIdleJob.class))
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(true)
+ .setPeriodic(TimeUnit.HOURS.toMillis(24)).build();
+
+ if (pending != null && !pending.equals(jobInfo)) {
+ jobScheduler.cancel(JOB_ID);
+ pending = null;
+ }
+
+ if (pending == null) {
+ jobScheduler.schedule(jobInfo);
+ }
+ }
+
+ public static void cancelJob(Context context) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ jobScheduler.cancel(JOB_ID);
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (BrightnessTracker.DEBUG) {
+ Slog.d(BrightnessTracker.TAG, "Scheduled write of brightness events");
+ }
+ DisplayManagerInternal dmi = LocalServices.getService(DisplayManagerInternal.class);
+ dmi.persistBrightnessSliderEvents();
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/com/android/server/display/BrightnessTracker.java b/com/android/server/display/BrightnessTracker.java
index 361d9284..2c6fe948 100644
--- a/com/android/server/display/BrightnessTracker.java
+++ b/com/android/server/display/BrightnessTracker.java
@@ -34,6 +34,7 @@ import android.net.Uri;
import android.os.BatteryManager;
import android.os.Environment;
import android.os.Handler;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -61,12 +62,12 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
-import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@@ -75,8 +76,8 @@ import java.util.concurrent.TimeUnit;
*/
public class BrightnessTracker {
- private static final String TAG = "BrightnessTracker";
- private static final boolean DEBUG = false;
+ static final String TAG = "BrightnessTracker";
+ static final boolean DEBUG = false;
private static final String EVENTS_FILE = "brightness_events.xml";
private static final int MAX_EVENTS = 100;
@@ -103,6 +104,8 @@ public class BrightnessTracker {
@GuardedBy("mEventsLock")
private RingBuffer<BrightnessChangeEvent> mEvents
= new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
+ @GuardedBy("mEventsLock")
+ private boolean mEventsDirty;
private final Runnable mEventsWriter = () -> writeEvents();
private volatile boolean mWriteEventsScheduled;
@@ -160,7 +163,10 @@ public class BrightnessTracker {
UserHandle.USER_CURRENT);
mSensorListener = new SensorListener();
- mInjector.registerSensorListener(mContext, mSensorListener);
+
+ if (mInjector.isInteractive(mContext)) {
+ mInjector.registerSensorListener(mContext, mSensorListener, mBgHandler);
+ }
mSettingsObserver = new SettingsObserver(mBgHandler);
mInjector.registerBrightnessObserver(mContentResolver, mSettingsObserver);
@@ -168,8 +174,12 @@ public class BrightnessTracker {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SHUTDOWN);
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
mBroadcastReceiver = new Receiver();
mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
+
+ mInjector.scheduleIdleJob(mContext);
}
/** Stop listening for events */
@@ -181,6 +191,7 @@ public class BrightnessTracker {
mInjector.unregisterSensorListener(mContext, mSensorListener);
mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver);
+ mInjector.cancelIdleJob(mContext);
}
/**
@@ -211,6 +222,10 @@ public class BrightnessTracker {
brightness, userId);
}
+ public void persistEvents() {
+ scheduleWriteEvents();
+ }
+
private void handleBrightnessChanged() {
if (DEBUG) {
Slog.d(TAG, "Brightness change");
@@ -278,6 +293,7 @@ public class BrightnessTracker {
Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
}
synchronized (mEventsLock) {
+ mEventsDirty = true;
mEvents.append(event);
}
}
@@ -291,8 +307,12 @@ public class BrightnessTracker {
private void writeEvents() {
mWriteEventsScheduled = false;
- // TODO kick off write on handler thread e.g. every 24 hours.
synchronized (mEventsLock) {
+ if (!mEventsDirty) {
+ // Nothing to write
+ return;
+ }
+
final AtomicFile writeTo = mInjector.getFile();
if (writeTo == null) {
return;
@@ -301,12 +321,14 @@ public class BrightnessTracker {
if (writeTo.exists()) {
writeTo.delete();
}
+ mEventsDirty = false;
} else {
FileOutputStream output = null;
try {
output = writeTo.startWrite();
writeEventsLocked(output);
writeTo.finishWrite(output);
+ mEventsDirty = false;
} catch (IOException e) {
writeTo.failWrite(output);
Slog.e(TAG, "Failed to write change mEvents.", e);
@@ -317,6 +339,8 @@ public class BrightnessTracker {
private void readEvents() {
synchronized (mEventsLock) {
+ // Read might prune events so mark as dirty.
+ mEventsDirty = true;
mEvents.clear();
final AtomicFile readFrom = mInjector.getFile();
if (readFrom != null && readFrom.exists()) {
@@ -344,13 +368,16 @@ public class BrightnessTracker {
out.startTag(null, TAG_EVENTS);
BrightnessChangeEvent[] toWrite = mEvents.toArray();
+ // Clear events, code below will add back the ones that are still within the time window.
+ mEvents.clear();
if (DEBUG) {
Slog.d(TAG, "Writing events " + toWrite.length);
}
- final long timeCutOff = System.currentTimeMillis() - MAX_EVENT_AGE;
+ final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE;
for (int i = 0; i < toWrite.length; ++i) {
int userSerialNo = mInjector.getUserSerialNumber(mUserManager, toWrite[i].userId);
if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) {
+ mEvents.append(toWrite[i]);
out.startTag(null, TAG_EVENT);
out.attribute(null, ATTR_BRIGHTNESS, Integer.toString(toWrite[i].brightness));
out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp));
@@ -465,6 +492,17 @@ public class BrightnessTracker {
}
}
+ public void dump(PrintWriter pw) {
+ synchronized (mEventsLock) {
+ pw.println("BrightnessTracker state:");
+ pw.println(" mEvents.size=" + mEvents.size());
+ pw.println(" mEventsDirty=" + mEventsDirty);
+ }
+ synchronized (mDataCollectionLock) {
+ pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size());
+ }
+ }
+
// Not allowed to keep the SensorEvent so used to copy the data we care about.
private static class LightData {
public float lux;
@@ -552,6 +590,11 @@ public class BrightnessTracker {
if (level != -1 && scale != 0) {
batteryLevelChanged(level, scale);
}
+ } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ mInjector.unregisterSensorListener(mContext, mSensorListener);
+ } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+ mInjector.registerSensorListener(mContext, mSensorListener,
+ mInjector.getBackgroundHandler());
}
}
}
@@ -559,11 +602,11 @@ public class BrightnessTracker {
@VisibleForTesting
static class Injector {
public void registerSensorListener(Context context,
- SensorEventListener sensorListener) {
+ SensorEventListener sensorListener, Handler handler) {
SensorManager sensorManager = context.getSystemService(SensorManager.class);
Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
sensorManager.registerListener(sensorListener,
- lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ lightSensor, SensorManager.SENSOR_DELAY_NORMAL, handler);
}
public void unregisterSensorListener(Context context, SensorEventListener sensorListener) {
@@ -635,5 +678,17 @@ public class BrightnessTracker {
public ActivityManager.StackInfo getFocusedStack() throws RemoteException {
return ActivityManager.getService().getFocusedStackInfo();
}
+
+ public void scheduleIdleJob(Context context) {
+ BrightnessIdleJob.scheduleJob(context);
+ }
+
+ public void cancelIdleJob(Context context) {
+ BrightnessIdleJob.cancelJob(context);
+ }
+
+ public boolean isInteractive(Context context) {
+ return context.getSystemService(PowerManager.class).isInteractive();
+ }
}
}
diff --git a/com/android/server/display/DisplayManagerService.java b/com/android/server/display/DisplayManagerService.java
index f1e20116..379aaadc 100644
--- a/com/android/server/display/DisplayManagerService.java
+++ b/com/android/server/display/DisplayManagerService.java
@@ -68,13 +68,13 @@ import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
-import android.view.WindowManagerInternal;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiThread;
+import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -1285,6 +1285,9 @@ public final class DisplayManagerService extends SystemService {
pw.println();
mPersistentDataStore.dump(pw);
+
+ pw.println();
+ mBrightnessTracker.dump(pw);
}
}
@@ -1921,5 +1924,10 @@ public final class DisplayManagerService extends SystemService {
public boolean isUidPresentOnDisplay(int uid, int displayId) {
return isUidPresentOnDisplayInternal(uid, displayId);
}
+
+ @Override
+ public void persistBrightnessSliderEvents() {
+ mBrightnessTracker.persistEvents();
+ }
}
}
diff --git a/com/android/server/display/DisplayPowerController.java b/com/android/server/display/DisplayPowerController.java
index 600bc42e..29a007a3 100644
--- a/com/android/server/display/DisplayPowerController.java
+++ b/com/android/server/display/DisplayPowerController.java
@@ -20,6 +20,7 @@ import android.app.ActivityManager;
import com.android.internal.app.IBatteryStats;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
+import com.android.server.policy.WindowManagerPolicy;
import android.animation.Animator;
import android.animation.ObjectAnimator;
@@ -43,7 +44,6 @@ import android.util.Slog;
import android.util.Spline;
import android.util.TimeUtils;
import android.view.Display;
-import android.view.WindowManagerPolicy;
import java.io.PrintWriter;
diff --git a/com/android/server/input/InputManagerService.java b/com/android/server/input/InputManagerService.java
index fa9b1078..4050790c 100644
--- a/com/android/server/input/InputManagerService.java
+++ b/com/android/server/input/InputManagerService.java
@@ -33,6 +33,7 @@ import com.android.internal.util.XmlUtils;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
+import com.android.server.policy.WindowManagerPolicy;
import org.xmlpull.v1.XmlPullParser;
@@ -98,7 +99,6 @@ import android.view.KeyEvent;
import android.view.PointerIcon;
import android.view.Surface;
import android.view.ViewConfiguration;
-import android.view.WindowManagerPolicy;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
diff --git a/com/android/server/job/JobSchedulerInternal.java b/com/android/server/job/JobSchedulerInternal.java
index 095526dc..9bcf208a 100644
--- a/com/android/server/job/JobSchedulerInternal.java
+++ b/com/android/server/job/JobSchedulerInternal.java
@@ -26,6 +26,18 @@ import java.util.List;
*/
public interface JobSchedulerInternal {
+ // Bookkeeping about app standby bucket scheduling
+
+ /**
+ * The current bucket heartbeat ordinal
+ */
+ long currentHeartbeat();
+
+ /**
+ * Heartbeat ordinal at which the given standby bucket's jobs next become runnable
+ */
+ long nextHeartbeatForBucket(int bucket);
+
/**
* Returns a list of pending jobs scheduled by the system service.
*/
diff --git a/com/android/server/job/JobSchedulerService.java b/com/android/server/job/JobSchedulerService.java
index b5497681..4af86a05 100644
--- a/com/android/server/job/JobSchedulerService.java
+++ b/com/android/server/job/JobSchedulerService.java
@@ -29,6 +29,9 @@ import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.app.job.JobWorkItem;
+import android.app.usage.AppStandby;
+import android.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -37,6 +40,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
@@ -46,7 +50,6 @@ import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -65,6 +68,7 @@ import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.DeviceIdleController;
@@ -111,6 +115,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
implements StateChangedListener, JobCompletedListener {
static final String TAG = "JobSchedulerService";
public static final boolean DEBUG = false;
+ public static final boolean DEBUG_STANDBY = DEBUG || false;
/** The maximum number of concurrent jobs we run at one time. */
private static final int MAX_JOB_CONTEXTS_COUNT = 16;
@@ -130,6 +135,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
final Object mLock = new Object();
/** Master list of jobs. */
final JobStore mJobs;
+ /** Tracking the standby bucket state of each app */
+ final StandbyTracker mStandbyTracker;
/** Tracking amount of time each package runs for. */
final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
@@ -151,6 +158,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
StorageController mStorageController;
/** Need directly for sending uid state changes */
private BackgroundJobsController mBackgroundJobsController;
+ private DeviceIdleJobsController mDeviceIdleJobsController;
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
* when ready to execute them.
@@ -162,8 +170,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
final JobHandler mHandler;
final JobSchedulerStub mJobSchedulerStub;
+ PackageManagerInternal mLocalPM;
IBatteryStats mBatteryStats;
- PowerManager mPowerManager;
DeviceIdleController.LocalService mLocalDeviceIdleController;
/**
@@ -192,6 +200,17 @@ public final class JobSchedulerService extends com.android.server.SystemService
*/
final SparseIntArray mBackingUpUids = new SparseIntArray();
+ /**
+ * Count standby heartbeats, and keep track of which beat each bucket's jobs will
+ * next become runnable. Index into this array is by normalized bucket:
+ * { ACTIVE, WORKING, FREQUENT, RARE, NEVER }. The ACTIVE and NEVER bucket
+ * milestones are not updated: ACTIVE apps get jobs whenever they ask for them,
+ * and NEVER apps don't get them at all.
+ */
+ final long[] mNextBucketHeartbeat = { 0, 0, 0, 0, Long.MAX_VALUE };
+ long mHeartbeat = 0;
+ long mLastHeartbeatTime = 0;
+
// -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
/**
@@ -236,6 +255,10 @@ public final class JobSchedulerService extends com.android.server.SystemService
private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count";
private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time";
private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time";
+ private static final String KEY_STANDBY_HEARTBEAT_TIME = "standby_heartbeat_time";
+ private static final String KEY_STANDBY_WORKING_BEATS = "standby_working_beats";
+ private static final String KEY_STANDBY_FREQUENT_BEATS = "standby_frequent_beats";
+ private static final String KEY_STANDBY_RARE_BEATS = "standby_rare_beats";
private static final int DEFAULT_MIN_IDLE_COUNT = 1;
private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -255,6 +278,10 @@ public final class JobSchedulerService extends com.android.server.SystemService
private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE;
private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
+ private static final long DEFAULT_STANDBY_HEARTBEAT_TIME = 11 * 60 * 1000L;
+ private static final int DEFAULT_STANDBY_WORKING_BEATS = 5; // ~ 1 hour, with 11-min beats
+ private static final int DEFAULT_STANDBY_FREQUENT_BEATS = 31; // ~ 6 hours
+ private static final int DEFAULT_STANDBY_RARE_BEATS = 130; // ~ 24 hours
/**
* Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -343,6 +370,27 @@ public final class JobSchedulerService extends com.android.server.SystemService
* The minimum backoff time to allow for exponential backoff.
*/
long MIN_EXP_BACKOFF_TIME = DEFAULT_MIN_EXP_BACKOFF_TIME;
+ /**
+ * How often we recalculate runnability based on apps' standby bucket assignment.
+ * This should be prime relative to common time interval lengths such as a quarter-
+ * hour or day, so that the heartbeat drifts relative to wall-clock milestones.
+ */
+ long STANDBY_HEARTBEAT_TIME = DEFAULT_STANDBY_HEARTBEAT_TIME;
+
+ /**
+ * Mapping: standby bucket -> number of heartbeats between each sweep of that
+ * bucket's jobs.
+ *
+ * Bucket assignments as recorded in the JobStatus objects are normalized to be
+ * indices into this array, rather than the raw constants used
+ * by AppIdleHistory.
+ */
+ final int[] STANDBY_BEATS = {
+ 0,
+ DEFAULT_STANDBY_WORKING_BEATS,
+ DEFAULT_STANDBY_FREQUENT_BEATS,
+ DEFAULT_STANDBY_RARE_BEATS
+ };
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -422,6 +470,14 @@ public final class JobSchedulerService extends com.android.server.SystemService
DEFAULT_MIN_LINEAR_BACKOFF_TIME);
MIN_EXP_BACKOFF_TIME = mParser.getLong(KEY_MIN_EXP_BACKOFF_TIME,
DEFAULT_MIN_EXP_BACKOFF_TIME);
+ STANDBY_HEARTBEAT_TIME = mParser.getLong(KEY_STANDBY_HEARTBEAT_TIME,
+ DEFAULT_STANDBY_HEARTBEAT_TIME);
+ STANDBY_BEATS[1] = mParser.getInt(KEY_STANDBY_WORKING_BEATS,
+ DEFAULT_STANDBY_WORKING_BEATS);
+ STANDBY_BEATS[2] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS,
+ DEFAULT_STANDBY_FREQUENT_BEATS);
+ STANDBY_BEATS[3] = mParser.getInt(KEY_STANDBY_RARE_BEATS,
+ DEFAULT_STANDBY_RARE_BEATS);
}
}
@@ -481,6 +537,17 @@ public final class JobSchedulerService extends com.android.server.SystemService
pw.print(" "); pw.print(KEY_MIN_EXP_BACKOFF_TIME); pw.print("=");
pw.print(MIN_EXP_BACKOFF_TIME); pw.println();
+
+ pw.print(" "); pw.print(KEY_STANDBY_HEARTBEAT_TIME); pw.print("=");
+ pw.print(STANDBY_HEARTBEAT_TIME); pw.println();
+
+ pw.print(" standby_beats={");
+ pw.print(STANDBY_BEATS[0]);
+ for (int i = 1; i < STANDBY_BEATS.length; i++) {
+ pw.print(", ");
+ pw.print(STANDBY_BEATS[i]);
+ }
+ pw.println('}');
}
}
@@ -623,13 +690,13 @@ public final class JobSchedulerService extends com.android.server.SystemService
cancelJobsForUid(uid, "uid gone");
}
synchronized (mLock) {
- mBackgroundJobsController.setUidActiveLocked(uid, false);
+ mDeviceIdleJobsController.setUidActiveLocked(uid, false);
}
}
@Override public void onUidActive(int uid) throws RemoteException {
synchronized (mLock) {
- mBackgroundJobsController.setUidActiveLocked(uid, true);
+ mDeviceIdleJobsController.setUidActiveLocked(uid, true);
}
}
@@ -638,7 +705,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
cancelJobsForUid(uid, "app uid idle");
}
synchronized (mLock) {
- mBackgroundJobsController.setUidActiveLocked(uid, false);
+ mDeviceIdleJobsController.setUidActiveLocked(uid, false);
}
}
@@ -683,6 +750,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
}
} catch (RemoteException e) {
}
+
synchronized (mLock) {
final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
@@ -934,9 +1002,22 @@ public final class JobSchedulerService extends com.android.server.SystemService
*/
public JobSchedulerService(Context context) {
super(context);
+
+ mLocalPM = LocalServices.getService(PackageManagerInternal.class);
+
mHandler = new JobHandler(context.getMainLooper());
mConstants = new Constants(mHandler);
mJobSchedulerStub = new JobSchedulerStub();
+
+ // Set up the app standby bucketing tracker
+ UsageStatsManagerInternal usageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+ mStandbyTracker = new StandbyTracker(usageStats);
+ usageStats.addAppIdleStateChangeListener(mStandbyTracker);
+
+ // The job store needs to call back
+ publishLocalService(JobSchedulerInternal.class, new LocalService());
+
+ // Initialize the job store and set up any persisted jobs
mJobs = JobStore.initAndGet(this);
// Create the controllers.
@@ -948,11 +1029,11 @@ public final class JobSchedulerService extends com.android.server.SystemService
mControllers.add(mBatteryController);
mStorageController = StorageController.get(this);
mControllers.add(mStorageController);
- mBackgroundJobsController = BackgroundJobsController.get(this);
- mControllers.add(mBackgroundJobsController);
+ mControllers.add(BackgroundJobsController.get(this));
mControllers.add(AppIdleController.get(this));
mControllers.add(ContentObserverController.get(this));
- mControllers.add(DeviceIdleJobsController.get(this));
+ mDeviceIdleJobsController = DeviceIdleJobsController.get(this);
+ mControllers.add(mDeviceIdleJobsController);
// If the job store determined that it can't yet reschedule persisted jobs,
// we need to start watching the clock.
@@ -1006,7 +1087,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
@Override
public void onStart() {
- publishLocalService(JobSchedulerInternal.class, new LocalService());
publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
}
@@ -1026,7 +1106,6 @@ public final class JobSchedulerService extends com.android.server.SystemService
final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
- mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
try {
ActivityManager.getService().registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
@@ -1206,7 +1285,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
}
delayMillis =
Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
- JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
+ JobStatus newJob = new JobStatus(failureToReschedule, getCurrentHeartbeat(),
+ elapsedNowMillis + delayMillis,
JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
for (int ic=0; ic<mControllers.size(); ic++) {
@@ -1220,10 +1300,12 @@ public final class JobSchedulerService extends com.android.server.SystemService
* Called after a periodic has executed so we can reschedule it. We take the last execution
* time of the job to be the time of completion (i.e. the time at which this function is
* called).
- * This could be inaccurate b/c the job can run for as long as
+ * <p>This could be inaccurate b/c the job can run for as long as
* {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
* to underscheduling at least, rather than if we had taken the last execution time to be the
* start of the execution.
+ * <p>Unlike a reschedule prior to execution, in this case we advance the next-heartbeat
+ * tracking as though the job were newly-scheduled.
* @return A new job representing the execution criteria for this instantiation of the
* recurring job.
*/
@@ -1245,8 +1327,9 @@ public final class JobSchedulerService extends com.android.server.SystemService
Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
}
- return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
- newLatestRuntimeElapsed, 0 /* backoffAttempt */,
+ return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
+ newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
+ 0 /* backoffAttempt */,
sSystemClock.millis() /* lastSuccessfulRunTime */,
periodicToReschedule.getLastFailedRunTime());
}
@@ -1393,7 +1476,9 @@ public final class JobSchedulerService extends com.android.server.SystemService
noteJobsNonpending(mPendingJobs);
mPendingJobs.clear();
stopNonReadyActiveJobsLocked();
+ boolean updated = updateStandbyHeartbeatLocked();
mJobs.forEachJob(mReadyQueueFunctor);
+ if (updated) updateNextStandbyHeartbeatsLocked();
mReadyQueueFunctor.postProcess();
if (DEBUG) {
@@ -1547,16 +1632,45 @@ public final class JobSchedulerService extends com.android.server.SystemService
noteJobsNonpending(mPendingJobs);
mPendingJobs.clear();
stopNonReadyActiveJobsLocked();
+ boolean updated = updateStandbyHeartbeatLocked();
mJobs.forEachJob(mMaybeQueueFunctor);
+ if (updated) updateNextStandbyHeartbeatsLocked();
mMaybeQueueFunctor.postProcess();
}
+ private boolean updateStandbyHeartbeatLocked() {
+ final long sinceLast = sElapsedRealtimeClock.millis() - mLastHeartbeatTime;
+ final long beatsElapsed = sinceLast / mConstants.STANDBY_HEARTBEAT_TIME;
+ if (beatsElapsed > 0) {
+ mHeartbeat += beatsElapsed;
+ mLastHeartbeatTime += beatsElapsed * mConstants.STANDBY_HEARTBEAT_TIME;
+ if (DEBUG_STANDBY) {
+ Slog.v(TAG, "Advancing standby heartbeat by " + beatsElapsed + " to " + mHeartbeat);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void updateNextStandbyHeartbeatsLocked() {
+ // don't update ACTIVE or NEVER bucket milestones
+ for (int i = 1; i < mNextBucketHeartbeat.length - 1; i++) {
+ while (mHeartbeat >= mNextBucketHeartbeat[i]) {
+ mNextBucketHeartbeat[i] += mConstants.STANDBY_BEATS[i];
+ }
+ if (DEBUG_STANDBY) {
+ Slog.v(TAG, " Bucket " + i + " next heartbeat " + mNextBucketHeartbeat[i]);
+ }
+ }
+ }
+
/**
* Criteria for moving a job into the pending queue:
* - It's ready.
* - It's not pending.
* - It's not already running on a JSC.
* - The user that requested the job is running.
+ * - The job's standby bucket has come due to be runnable.
* - The component is enabled and runnable.
*/
private boolean isReadyToBeExecutedLocked(JobStatus job) {
@@ -1605,6 +1719,22 @@ public final class JobSchedulerService extends com.android.server.SystemService
return false;
}
+ // If the app is in a non-active standby bucket, make sure we've waited
+ // an appropriate amount of time since the last invocation
+ if (mHeartbeat < mNextBucketHeartbeat[job.getStandbyBucket()]) {
+ // TODO: log/trace that we're deferring the job due to bucketing if we hit this
+ if (job.getWhenStandbyDeferred() == 0) {
+ if (DEBUG_STANDBY) {
+ Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < "
+ + mNextBucketHeartbeat[job.getStandbyBucket()] + " for " + job);
+ }
+ job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
+ }
+ return false;
+ }
+
+ // The expensive check last: validate that the defined package+service is
+ // still present & viable.
final boolean componentPresent;
try {
componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
@@ -1818,6 +1948,22 @@ public final class JobSchedulerService extends com.android.server.SystemService
final class LocalService implements JobSchedulerInternal {
/**
+ * The current bucket heartbeat ordinal
+ */
+ public long currentHeartbeat() {
+ return getCurrentHeartbeat();
+ }
+
+ /**
+ * Heartbeat ordinal at which the given standby bucket's jobs next become runnable
+ */
+ public long nextHeartbeatForBucket(int bucket) {
+ synchronized (mLock) {
+ return mNextBucketHeartbeat[bucket];
+ }
+ }
+
+ /**
* Returns a list of all pending jobs. A running job is not considered pending. Periodic
* jobs are always considered pending.
*/
@@ -1878,6 +2024,79 @@ public final class JobSchedulerService extends com.android.server.SystemService
}
/**
+ * Tracking of app assignments to standby buckets
+ */
+ final class StandbyTracker extends AppIdleStateChangeListener {
+ final UsageStatsManagerInternal mUsageStats;
+
+ StandbyTracker(UsageStatsManagerInternal usageStats) {
+ mUsageStats = usageStats;
+ }
+
+ // AppIdleStateChangeListener interface for live updates
+
+ @Override
+ public void onAppIdleStateChanged(final String packageName, final int userId,
+ boolean idle, int bucket) {
+ final int uid = mLocalPM.getPackageUid(packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+ if (uid < 0) {
+ if (DEBUG_STANDBY) {
+ Slog.i(TAG, "App idle state change for unknown app "
+ + packageName + "/" + userId);
+ }
+ return;
+ }
+
+ final int bucketIndex = standbyBucketToBucketIndex(bucket);
+ // update job bookkeeping out of band
+ BackgroundThread.getHandler().post(() -> {
+ if (DEBUG_STANDBY) {
+ Slog.i(TAG, "Moving uid " + uid + " to bucketIndex " + bucketIndex);
+ }
+ synchronized (mLock) {
+ // TODO: update to be more efficient once we can slice by source UID
+ mJobs.forEachJob((JobStatus job) -> {
+ if (job.getSourceUid() == uid) {
+ job.setStandbyBucket(bucketIndex);
+ }
+ });
+ onControllerStateChanged();
+ }
+ });
+ }
+
+ @Override
+ public void onParoleStateChanged(boolean isParoleOn) {
+ // Unused
+ }
+ }
+
+ public static int standbyBucketToBucketIndex(int bucket) {
+ // Normalize AppStandby constants to indices into our bookkeeping
+ if (bucket == AppStandby.STANDBY_BUCKET_NEVER) return 4;
+ else if (bucket >= AppStandby.STANDBY_BUCKET_RARE) return 3;
+ else if (bucket >= AppStandby.STANDBY_BUCKET_FREQUENT) return 2;
+ else if (bucket >= AppStandby.STANDBY_BUCKET_WORKING_SET) return 1;
+ else return 0;
+ }
+
+ public static int standbyBucketForPackage(String packageName, int userId, long elapsedNow) {
+ UsageStatsManagerInternal usageStats = LocalServices.getService(
+ UsageStatsManagerInternal.class);
+ int bucket = usageStats != null
+ ? usageStats.getAppStandbyBucket(packageName, userId, elapsedNow)
+ : 0;
+
+ bucket = standbyBucketToBucketIndex(bucket);
+
+ if (DEBUG_STANDBY) {
+ Slog.v(TAG, packageName + "/" + userId + " standby bucket index: " + bucket);
+ }
+ return bucket;
+ }
+
+ /**
* Binder stub trampoline implementation
*/
final class JobSchedulerStub extends IJobScheduler.Stub {
@@ -1942,6 +2161,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
}
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(uid);
enforceValidJobRequest(uid, job);
if (job.isPersisted()) {
@@ -1958,7 +2178,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
long ident = Binder.clearCallingIdentity();
try {
- return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, -1, null);
+ return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
+ null);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1970,8 +2191,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
if (DEBUG) {
Slog.d(TAG, "Enqueueing job: " + job.toString() + " work: " + work);
}
- final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(uid);
enforceValidJobRequest(uid, job);
if (job.isPersisted()) {
@@ -1988,7 +2209,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
long ident = Binder.clearCallingIdentity();
try {
- return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, -1, null);
+ return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId,
+ null);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2000,7 +2222,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
final int callerUid = Binder.getCallingUid();
if (DEBUG) {
Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
- + " on behalf of " + packageName);
+ + " on behalf of " + packageName + "/");
}
if (packageName == null) {
@@ -2234,6 +2456,12 @@ public final class JobSchedulerService extends com.android.server.SystemService
}
}
+ long getCurrentHeartbeat() {
+ synchronized (mLock) {
+ return mHeartbeat;
+ }
+ }
+
int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
try {
final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
diff --git a/com/android/server/job/JobServiceContext.java b/com/android/server/job/JobServiceContext.java
index ac7297e6..6a3fd04a 100644
--- a/com/android/server/job/JobServiceContext.java
+++ b/com/android/server/job/JobServiceContext.java
@@ -64,6 +64,8 @@ import com.android.server.job.controllers.JobStatus;
*/
public final class JobServiceContext implements ServiceConnection {
private static final boolean DEBUG = JobSchedulerService.DEBUG;
+ private static final boolean DEBUG_STANDBY = JobSchedulerService.DEBUG_STANDBY;
+
private static final String TAG = "JobServiceContext";
/** Amount of time a job is allowed to execute for before being considered timed-out. */
public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins.
@@ -220,6 +222,17 @@ public final class JobServiceContext implements ServiceConnection {
isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
+ if (DEBUG_STANDBY) {
+ final long whenDeferred = job.getWhenStandbyDeferred();
+ if (whenDeferred > 0) {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Starting job deferred for standby by ");
+ TimeUtils.formatDuration(mExecutionStartTimeElapsed - whenDeferred, sb);
+ sb.append(" : ");
+ sb.append(job.toShortString());
+ Slog.v(TAG, sb.toString());
+ }
+ }
// Once we'e begun executing a job, we by definition no longer care whether
// it was inflated from disk with not-yet-coherent delay/deadline bounds.
job.clearPersistedUtcTimes();
diff --git a/com/android/server/job/JobStore.java b/com/android/server/job/JobStore.java
index 1af3b39e..219bc611 100644
--- a/com/android/server/job/JobStore.java
+++ b/com/android/server/job/JobStore.java
@@ -43,6 +43,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.IoThread;
+import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
import com.android.server.job.controllers.JobStatus;
@@ -174,7 +175,8 @@ public final class JobStore {
if (utcTimes != null) {
Pair<Long, Long> elapsedRuntimes =
convertRtcBoundsToElapsed(utcTimes, elapsedNow);
- toAdd.add(new JobStatus(job, elapsedRuntimes.first, elapsedRuntimes.second,
+ toAdd.add(new JobStatus(job, job.getBaseHeartbeat(),
+ elapsedRuntimes.first, elapsedRuntimes.second,
0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime()));
toRemove.add(job);
}
@@ -250,7 +252,7 @@ public final class JobStore {
/**
* @param userHandle User for whom we are querying the list of jobs.
- * @return A list of all the jobs scheduled by the provided user. Never null.
+ * @return A list of all the jobs scheduled for the provided user. Never null.
*/
public List<JobStatus> getJobsByUser(int userHandle) {
return mJobSet.getJobsByUser(userHandle);
@@ -287,6 +289,10 @@ public final class JobStore {
mJobSet.forEachJob(uid, functor);
}
+ public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+ mJobSet.forEachJobForSourceUid(sourceUid, functor);
+ }
+
public interface JobStatusFunctor {
public void process(JobStatus jobStatus);
}
@@ -842,8 +848,13 @@ public final class JobStore {
}
// And now we're done
+ JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
+ final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
+ sourceUserId, elapsedNow);
+ long currentHeartbeat = service != null ? service.currentHeartbeat() : 0;
JobStatus js = new JobStatus(
- jobBuilder.build(), uid, sourcePackageName, sourceUserId, sourceTag,
+ jobBuilder.build(), uid, sourcePackageName, sourceUserId,
+ appBucket, currentHeartbeat, sourceTag,
elapsedRuntimes.first, elapsedRuntimes.second,
lastSuccessfulRunTime, lastFailedRunTime,
(rtcIsGood) ? null : rtcRuntimes);
@@ -979,9 +990,12 @@ public final class JobStore {
static final class JobSet {
// Key is the getUid() originator of the jobs in each sheaf
private SparseArray<ArraySet<JobStatus>> mJobs;
+ // Same data but with the key as getSourceUid() of the jobs in each sheaf
+ private SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid;
public JobSet() {
mJobs = new SparseArray<ArraySet<JobStatus>>();
+ mJobsPerSourceUid = new SparseArray<>();
}
public List<JobStatus> getJobsByUid(int uid) {
@@ -995,10 +1009,10 @@ public final class JobStore {
// By user, not by uid, so we need to traverse by key and check
public List<JobStatus> getJobsByUser(int userId) {
- ArrayList<JobStatus> result = new ArrayList<JobStatus>();
- for (int i = mJobs.size() - 1; i >= 0; i--) {
- if (UserHandle.getUserId(mJobs.keyAt(i)) == userId) {
- ArraySet<JobStatus> jobs = mJobs.valueAt(i);
+ final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
+ for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
+ final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
if (jobs != null) {
result.addAll(jobs);
}
@@ -1009,32 +1023,60 @@ public final class JobStore {
public boolean add(JobStatus job) {
final int uid = job.getUid();
+ final int sourceUid = job.getSourceUid();
ArraySet<JobStatus> jobs = mJobs.get(uid);
if (jobs == null) {
jobs = new ArraySet<JobStatus>();
mJobs.put(uid, jobs);
}
- return jobs.add(job);
+ ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
+ if (jobsForSourceUid == null) {
+ jobsForSourceUid = new ArraySet<>();
+ mJobsPerSourceUid.put(sourceUid, jobsForSourceUid);
+ }
+ return jobs.add(job) && jobsForSourceUid.add(job);
}
public boolean remove(JobStatus job) {
final int uid = job.getUid();
- ArraySet<JobStatus> jobs = mJobs.get(uid);
- boolean didRemove = (jobs != null) ? jobs.remove(job) : false;
- if (didRemove && jobs.size() == 0) {
- // no more jobs for this uid; let the now-empty set object be GC'd.
- mJobs.remove(uid);
+ final ArraySet<JobStatus> jobs = mJobs.get(uid);
+ final int sourceUid = job.getSourceUid();
+ final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
+ boolean didRemove = jobs != null && jobs.remove(job) && jobsForSourceUid.remove(job);
+ if (didRemove) {
+ if (jobs.size() == 0) {
+ // no more jobs for this uid; let the now-empty set object be GC'd.
+ mJobs.remove(uid);
+ }
+ if (jobsForSourceUid.size() == 0) {
+ mJobsPerSourceUid.remove(sourceUid);
+ }
+ return true;
}
- return didRemove;
+ return false;
}
- // Remove the jobs all users not specified by the whitelist of user ids
+ /**
+ * Removes the jobs of all users not specified by the whitelist of user ids.
+ * The jobs scheduled by non existent users will not be removed if they were
+ */
public void removeJobsOfNonUsers(int[] whitelist) {
- for (int jobIndex = mJobs.size() - 1; jobIndex >= 0; jobIndex--) {
- int jobUserId = UserHandle.getUserId(mJobs.keyAt(jobIndex));
- // check if job's user id is not in the whitelist
+ for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
+ final int jobUserId = UserHandle.getUserId(mJobsPerSourceUid.keyAt(jobSetIndex));
if (!ArrayUtils.contains(whitelist, jobUserId)) {
- mJobs.removeAt(jobIndex);
+ mJobsPerSourceUid.removeAt(jobSetIndex);
+ }
+ }
+ for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
+ final ArraySet<JobStatus> jobsForUid = mJobs.valueAt(jobSetIndex);
+ for (int jobIndex = jobsForUid.size() - 1; jobIndex >= 0; jobIndex--) {
+ final int jobUserId = jobsForUid.valueAt(jobIndex).getUserId();
+ if (!ArrayUtils.contains(whitelist, jobUserId)) {
+ jobsForUid.removeAt(jobIndex);
+ }
+ }
+ if (jobsForUid.size() == 0) {
+ mJobs.removeAt(jobSetIndex);
}
}
}
@@ -1077,6 +1119,7 @@ public final class JobStore {
public void clear() {
mJobs.clear();
+ mJobsPerSourceUid.clear();
}
public int size() {
@@ -1112,8 +1155,17 @@ public final class JobStore {
}
}
- public void forEachJob(int uid, JobStatusFunctor functor) {
- ArraySet<JobStatus> jobs = mJobs.get(uid);
+ public void forEachJob(int callingUid, JobStatusFunctor functor) {
+ ArraySet<JobStatus> jobs = mJobs.get(callingUid);
+ if (jobs != null) {
+ for (int i = jobs.size() - 1; i >= 0; i--) {
+ functor.process(jobs.valueAt(i));
+ }
+ }
+ }
+
+ public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+ final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
if (jobs != null) {
for (int i = jobs.size() - 1; i >= 0; i--) {
functor.process(jobs.valueAt(i));
diff --git a/com/android/server/job/controllers/AppIdleController.java b/com/android/server/job/controllers/AppIdleController.java
index caa85220..a7ed2f56 100644
--- a/com/android/server/job/controllers/AppIdleController.java
+++ b/com/android/server/job/controllers/AppIdleController.java
@@ -180,6 +180,7 @@ public final class AppIdleController extends StateController {
if (mAppIdleParoleOn) {
return;
}
+
PackageUpdateFunc update = new PackageUpdateFunc(userId, packageName, idle);
mJobSchedulerService.getJobStore().forEachJob(update);
if (update.mChanged) {
diff --git a/com/android/server/job/controllers/BackgroundJobsController.java b/com/android/server/job/controllers/BackgroundJobsController.java
index 78b4160e..fc4015d0 100644
--- a/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/com/android/server/job/controllers/BackgroundJobsController.java
@@ -16,24 +16,16 @@
package com.android.server.job.controllers;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.os.IDeviceIdleController;
-import android.os.PowerManager;
-import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.UserHandle;
-import android.util.ArraySet;
import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsService;
import com.android.internal.util.ArrayUtils;
+import com.android.server.ForceAppStandbyTracker;
+import com.android.server.ForceAppStandbyTracker.Listener;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobStore;
@@ -49,18 +41,10 @@ public final class BackgroundJobsController extends StateController {
private static volatile BackgroundJobsController sController;
private final JobSchedulerService mJobSchedulerService;
- private final IAppOpsService mAppOpsService;
private final IDeviceIdleController mDeviceIdleController;
- private final SparseBooleanArray mForegroundUids;
- private int[] mPowerWhitelistedUserAppIds;
- private int[] mTempWhitelistedAppIds;
- /**
- * Only tracks jobs for which source package app op RUN_ANY_IN_BACKGROUND is not ALLOWED.
- * Maps jobs to the sourceUid unlike the global {@link JobSchedulerService#mJobs JobStore}
- * which uses callingUid.
- */
- private SparseArray<ArraySet<JobStatus>> mTrackedJobs;
+ private final ForceAppStandbyTracker mForceAppStandbyTracker;
+
public static BackgroundJobsController get(JobSchedulerService service) {
synchronized (sCreationLock) {
@@ -72,232 +56,148 @@ public final class BackgroundJobsController extends StateController {
}
}
- private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (mLock) {
- try {
- switch (intent.getAction()) {
- case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
- mPowerWhitelistedUserAppIds =
- mDeviceIdleController.getAppIdUserWhitelist();
- break;
- case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
- mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
- break;
- }
- } catch (RemoteException rexc) {
- Slog.e(LOG_TAG, "Device idle controller not reachable");
- }
- if (checkAllTrackedJobsLocked()) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
- }
- };
-
private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) {
super(service, context, lock);
mJobSchedulerService = service;
- mAppOpsService = IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE));
mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- mForegroundUids = new SparseBooleanArray();
- mTrackedJobs = new SparseArray<>();
- try {
- mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
- new AppOpsWatcher());
- mPowerWhitelistedUserAppIds = mDeviceIdleController.getAppIdUserWhitelist();
- mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
- } catch (RemoteException rexc) {
- // Shouldn't happen as they are in the same process.
- Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc);
- }
- IntentFilter powerWhitelistFilter = new IntentFilter();
- powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
- powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
- context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter,
- null, null);
+ mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+
+ mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
+ mForceAppStandbyTracker.start();
}
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
- final int uid = jobStatus.getSourceUid();
- final String packageName = jobStatus.getSourcePackageName();
- try {
- final int mode = mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
- uid, packageName);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true);
- return;
- }
- } catch (RemoteException rexc) {
- Slog.e(LOG_TAG, "Cannot reach app ops service", rexc);
- }
- jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRunJobLocked(uid));
- startTrackingJobLocked(jobStatus);
+ updateSingleJobRestrictionLocked(jobStatus);
}
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate) {
- stopTrackingJobLocked(jobStatus);
- }
-
- /* Called by JobSchedulerService to report uid state changes between active and idle */
- public void setUidActiveLocked(int uid, boolean active) {
- final boolean changed = (active != mForegroundUids.get(uid));
- if (!changed) {
- return;
- }
- if (DEBUG) {
- Slog.d(LOG_TAG, "uid " + uid + " going to " + (active ? "fg" : "bg"));
- }
- if (active) {
- mForegroundUids.put(uid, true);
- } else {
- mForegroundUids.delete(uid);
- }
- if (checkTrackedJobsForUidLocked(uid)) {
- mStateChangedListener.onControllerStateChanged();
- }
}
@Override
public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
pw.println("BackgroundJobsController");
- pw.print("Foreground uids: [");
- for (int i = 0; i < mForegroundUids.size(); i++) {
- if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
- }
- pw.println("]");
- mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
- @Override
- public void process(JobStatus jobStatus) {
- if (!jobStatus.shouldDump(filterUid)) {
- return;
- }
- final int uid = jobStatus.getSourceUid();
- pw.print(" #");
- jobStatus.printUniqueId(pw);
- pw.print(" from ");
- UserHandle.formatUid(pw, uid);
- pw.print(mForegroundUids.get(uid) ? " foreground" : " background");
- if (isWhitelistedLocked(uid)) {
- pw.print(", whitelisted");
- }
- pw.print(": ");
- pw.print(jobStatus.getSourcePackageName());
- pw.print(" [background restrictions");
- final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
- pw.print(jobsForUid != null && jobsForUid.contains(jobStatus) ? " on]" : " off]");
- if ((jobStatus.satisfiedConstraints
- & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
- pw.println(" RUNNABLE");
- } else {
- pw.println(" WAITING");
- }
+
+ mForceAppStandbyTracker.dump(pw, "");
+
+ pw.println("Job state:");
+ mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
+ if (!jobStatus.shouldDump(filterUid)) {
+ return;
+ }
+ final int uid = jobStatus.getSourceUid();
+ pw.print(" #");
+ jobStatus.printUniqueId(pw);
+ pw.print(" from ");
+ UserHandle.formatUid(pw, uid);
+ pw.print(mForceAppStandbyTracker.isInForeground(uid) ? " foreground" : " background");
+ if (mForceAppStandbyTracker.isUidPowerSaveWhitelisted(uid) ||
+ mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(uid)) {
+ pw.print(", whitelisted");
+ }
+ pw.print(": ");
+ pw.print(jobStatus.getSourcePackageName());
+
+ pw.print(" [RUN_ANY_IN_BACKGROUND ");
+ pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(
+ jobStatus.getSourceUid(), jobStatus.getSourcePackageName())
+ ? "allowed]" : "disallowed]");
+
+ if ((jobStatus.satisfiedConstraints
+ & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
+ pw.println(" RUNNABLE");
+ } else {
+ pw.println(" WAITING");
}
});
}
- void startTrackingJobLocked(JobStatus jobStatus) {
- final int uid = jobStatus.getSourceUid();
- ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
- if (jobsForUid == null) {
- jobsForUid = new ArraySet<>();
- mTrackedJobs.put(uid, jobsForUid);
- }
- jobsForUid.add(jobStatus);
+ private void updateAllJobRestrictionsLocked() {
+ updateJobRestrictionsLocked(/*filterUid=*/ -1);
}
- void stopTrackingJobLocked(JobStatus jobStatus) {
- final int uid = jobStatus.getSourceUid();
- ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
- if (jobsForUid != null) {
- jobsForUid.remove(jobStatus);
- }
+ private void updateJobRestrictionsForUidLocked(int uid) {
+
+ // TODO Use forEachJobForSourceUid() once we have it.
+
+ updateJobRestrictionsLocked(/*filterUid=*/ uid);
}
- boolean checkAllTrackedJobsLocked() {
- boolean changed = false;
- for (int i = 0; i < mTrackedJobs.size(); i++) {
- changed |= checkTrackedJobsForUidLocked(mTrackedJobs.keyAt(i));
+ private void updateJobRestrictionsLocked(int filterUid) {
+ final UpdateJobFunctor updateTrackedJobs =
+ new UpdateJobFunctor(filterUid);
+
+ final long start = DEBUG ? SystemClock.elapsedRealtimeNanos() : 0;
+
+ mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs);
+
+ final long time = DEBUG ? (SystemClock.elapsedRealtimeNanos() - start) : 0;
+ if (DEBUG) {
+ Slog.d(LOG_TAG, String.format(
+ "Job status updated: %d/%d checked/total jobs, %d us",
+ updateTrackedJobs.mCheckedCount,
+ updateTrackedJobs.mTotalCount,
+ (time / 1000)
+ ));
}
- return changed;
- }
- private boolean checkTrackedJobsForUidLocked(int uid) {
- final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
- boolean changed = false;
- if (jobsForUid != null) {
- for (int i = 0; i < jobsForUid.size(); i++) {
- JobStatus jobStatus = jobsForUid.valueAt(i);
- changed |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(
- canRunJobLocked(uid));
- }
+ if (updateTrackedJobs.mChanged) {
+ mStateChangedListener.onControllerStateChanged();
}
- return changed;
}
- boolean isWhitelistedLocked(int uid) {
- return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid))
- || ArrayUtils.contains(mPowerWhitelistedUserAppIds, UserHandle.getAppId(uid));
- }
+ boolean updateSingleJobRestrictionLocked(JobStatus jobStatus) {
- boolean canRunJobLocked(int uid) {
- return mForegroundUids.get(uid) || isWhitelistedLocked(uid);
- }
+ final int uid = jobStatus.getSourceUid();
+ final String packageName = jobStatus.getSourcePackageName();
- private final class AppOpsWatcher extends IAppOpsCallback.Stub {
- @Override
- public void opChanged(int op, int uid, String packageName) throws RemoteException {
- synchronized (mLock) {
- final int mode = mAppOpsService.checkOperation(op, uid, packageName);
- if (DEBUG) {
- Slog.d(LOG_TAG,
- "Appop changed for " + uid + ", " + packageName + " to " + mode);
- }
- final boolean shouldTrack = (mode != AppOpsManager.MODE_ALLOWED);
- UpdateTrackedJobsFunc updateTrackedJobs = new UpdateTrackedJobsFunc(uid,
- packageName, shouldTrack);
- mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs);
- if (updateTrackedJobs.mChanged) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
- }
+ final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName);
+
+ return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
}
- private final class UpdateTrackedJobsFunc implements JobStore.JobStatusFunctor {
- private final String mPackageName;
- private final int mUid;
- private final boolean mShouldTrack;
- private boolean mChanged = false;
+ private final class UpdateJobFunctor implements JobStore.JobStatusFunctor {
+ private final int mFilterUid;
+
+ boolean mChanged = false;
+ int mTotalCount = 0;
+ int mCheckedCount = 0;
- UpdateTrackedJobsFunc(int uid, String packageName, boolean shouldTrack) {
- mUid = uid;
- mPackageName = packageName;
- mShouldTrack = shouldTrack;
+ UpdateJobFunctor(int filterUid) {
+ mFilterUid = filterUid;
}
@Override
public void process(JobStatus jobStatus) {
- final String packageName = jobStatus.getSourcePackageName();
- final int uid = jobStatus.getSourceUid();
- if (mUid != uid || !mPackageName.equals(packageName)) {
+ mTotalCount++;
+ if ((mFilterUid > 0) && (mFilterUid != jobStatus.getSourceUid())) {
return;
}
- if (mShouldTrack) {
- mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(
- canRunJobLocked(uid));
- startTrackingJobLocked(jobStatus);
- } else {
- mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true);
- stopTrackingJobLocked(jobStatus);
+ mCheckedCount++;
+ if (updateSingleJobRestrictionLocked(jobStatus)) {
+ mChanged = true;
}
}
}
+
+ private final Listener mForceAppStandbyListener = new Listener() {
+ @Override
+ public void updateAllJobs() {
+ updateAllJobRestrictionsLocked();
+ }
+
+ @Override
+ public void updateJobsForUid(int uid) {
+ updateJobRestrictionsForUidLocked(uid);
+ }
+
+ @Override
+ public void updateJobsForUidPackage(int uid, String packageName) {
+ updateJobRestrictionsForUidLocked(uid);
+ }
+ };
}
diff --git a/com/android/server/job/controllers/DeviceIdleJobsController.java b/com/android/server/job/controllers/DeviceIdleJobsController.java
index 374ab43c..b7eb9e06 100644
--- a/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -16,14 +16,19 @@
package com.android.server.job.controllers;
+import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import com.android.internal.util.ArrayUtils;
import com.android.server.DeviceIdleController;
@@ -42,11 +47,22 @@ public final class DeviceIdleJobsController extends StateController {
private static final String LOG_TAG = "DeviceIdleJobsController";
private static final boolean LOG_DEBUG = false;
+ private static final long BACKGROUND_JOBS_DELAY = 3000;
+
+ static final int PROCESS_BACKGROUND_JOBS = 1;
// Singleton factory
private static Object sCreationLock = new Object();
private static DeviceIdleJobsController sController;
+ /**
+ * These are jobs added with a special flag to indicate that they should be exempted from doze
+ * when the app is temp whitelisted or in the foreground.
+ */
+ private final ArraySet<JobStatus> mAllowInIdleJobs;
+ private final SparseBooleanArray mForegroundUids;
+ private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
+ private final DeviceIdleJobsDelayHandler mHandler;
private final JobSchedulerService mJobSchedulerService;
private final PowerManager mPowerManager;
private final DeviceIdleController.LocalService mLocalDeviceIdleController;
@@ -57,14 +73,6 @@ public final class DeviceIdleJobsController extends StateController {
private boolean mDeviceIdleMode;
private int[] mDeviceIdleWhitelistAppIds;
private int[] mPowerSaveTempWhitelistAppIds;
- // These jobs were added when the app was in temp whitelist, these should be exempted from doze
- private final ArraySet<JobStatus> mTempWhitelistedJobs;
-
- final JobStore.JobStatusFunctor mUpdateFunctor = new JobStore.JobStatusFunctor() {
- @Override public void process(JobStatus jobStatus) {
- updateTaskStateLocked(jobStatus);
- }
- };
/**
* Returns a singleton for the DeviceIdleJobsController
@@ -108,8 +116,8 @@ public final class DeviceIdleJobsController extends StateController {
+ Arrays.toString(mPowerSaveTempWhitelistAppIds));
}
boolean changed = false;
- for (int i = 0; i < mTempWhitelistedJobs.size(); i ++) {
- changed |= updateTaskStateLocked(mTempWhitelistedJobs.valueAt(i));
+ for (int i = 0; i < mAllowInIdleJobs.size(); i++) {
+ changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i));
}
if (changed) {
mStateChangedListener.onControllerStateChanged();
@@ -125,6 +133,7 @@ public final class DeviceIdleJobsController extends StateController {
super(jobSchedulerService, context, lock);
mJobSchedulerService = jobSchedulerService;
+ mHandler = new DeviceIdleJobsDelayHandler(context.getMainLooper());
// Register for device idle mode changes
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mLocalDeviceIdleController =
@@ -132,7 +141,9 @@ public final class DeviceIdleJobsController extends StateController {
mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
mPowerSaveTempWhitelistAppIds =
mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
- mTempWhitelistedJobs = new ArraySet<>();
+ mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor();
+ mAllowInIdleJobs = new ArraySet<>();
+ mForegroundUids = new SparseBooleanArray();
final IntentFilter filter = new IntentFilter();
filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
@@ -150,7 +161,20 @@ public final class DeviceIdleJobsController extends StateController {
}
mDeviceIdleMode = enabled;
if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
- mJobSchedulerService.getJobStore().forEachJob(mUpdateFunctor);
+ if (enabled) {
+ mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
+ mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
+ } else {
+ // When coming out of doze, process all foreground uids immediately, while others
+ // will be processed after a delay of 3 seconds.
+ for (int i = 0; i < mForegroundUids.size(); i++) {
+ if (mForegroundUids.valueAt(i)) {
+ mJobSchedulerService.getJobStore().forEachJobForSourceUid(
+ mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
+ }
+ }
+ mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
+ }
}
// Inform the job scheduler service about idle mode changes
if (changed) {
@@ -159,11 +183,30 @@ public final class DeviceIdleJobsController extends StateController {
}
/**
+ * Called by jobscheduler service to report uid state changes between active and idle
+ */
+ public void setUidActiveLocked(int uid, boolean active) {
+ final boolean changed = (active != mForegroundUids.get(uid));
+ if (!changed) {
+ return;
+ }
+ if (LOG_DEBUG) {
+ Slog.d(LOG_TAG, "uid " + uid + " going " + (active ? "active" : "inactive"));
+ }
+ mForegroundUids.put(uid, active);
+ mDeviceIdleUpdateFunctor.mChanged = false;
+ mJobSchedulerService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor);
+ if (mDeviceIdleUpdateFunctor.mChanged) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+
+ /**
* Checks if the given job's scheduling app id exists in the device idle user whitelist.
*/
boolean isWhitelistedLocked(JobStatus job) {
- return ArrayUtils.contains(mDeviceIdleWhitelistAppIds,
- UserHandle.getAppId(job.getSourceUid()));
+ return Arrays.binarySearch(mDeviceIdleWhitelistAppIds,
+ UserHandle.getAppId(job.getSourceUid())) >= 0;
}
/**
@@ -175,31 +218,33 @@ public final class DeviceIdleJobsController extends StateController {
}
private boolean updateTaskStateLocked(JobStatus task) {
- final boolean whitelisted = isWhitelistedLocked(task)
- || (mTempWhitelistedJobs.contains(task) && isTempWhitelistedLocked(task));
- final boolean enableTask = !mDeviceIdleMode || whitelisted;
+ final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
+ && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
+ final boolean whitelisted = isWhitelistedLocked(task);
+ final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
}
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
- if (isTempWhitelistedLocked(jobStatus)) {
- mTempWhitelistedJobs.add(jobStatus);
- jobStatus.setDeviceNotDozingConstraintSatisfied(true, true);
- } else {
- updateTaskStateLocked(jobStatus);
+ if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
+ mAllowInIdleJobs.add(jobStatus);
}
+ updateTaskStateLocked(jobStatus);
}
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate) {
- mTempWhitelistedJobs.remove(jobStatus);
+ if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
+ mAllowInIdleJobs.remove(jobStatus);
+ }
}
@Override
public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
pw.println("DeviceIdleJobsController");
+ pw.println("mDeviceIdleMode=" + mDeviceIdleMode);
mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
@Override public void process(JobStatus jobStatus) {
if (!jobStatus.shouldDump(filterUid)) {
@@ -217,8 +262,42 @@ public final class DeviceIdleJobsController extends StateController {
if (jobStatus.dozeWhitelisted) {
pw.print(" WHITELISTED");
}
+ if (mAllowInIdleJobs.contains(jobStatus)) {
+ pw.print(" ALLOWED_IN_DOZE");
+ }
pw.println();
}
});
}
+
+ final class DeviceIdleUpdateFunctor implements JobStore.JobStatusFunctor {
+ boolean mChanged;
+
+ @Override
+ public void process(JobStatus jobStatus) {
+ mChanged |= updateTaskStateLocked(jobStatus);
+ }
+ }
+
+ final class DeviceIdleJobsDelayHandler extends Handler {
+ public DeviceIdleJobsDelayHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case PROCESS_BACKGROUND_JOBS:
+ // Just process all the jobs, the ones in foreground should already be running.
+ synchronized (mLock) {
+ mDeviceIdleUpdateFunctor.mChanged = false;
+ mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
+ if (mDeviceIdleUpdateFunctor.mChanged) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+ break;
+ }
+ }
+ }
} \ No newline at end of file
diff --git a/com/android/server/job/controllers/JobStatus.java b/com/android/server/job/controllers/JobStatus.java
index a5906cb1..e71b8ec4 100644
--- a/com/android/server/job/controllers/JobStatus.java
+++ b/com/android/server/job/controllers/JobStatus.java
@@ -34,7 +34,9 @@ import android.util.Pair;
import android.util.Slog;
import android.util.TimeUtils;
+import com.android.server.LocalServices;
import com.android.server.job.GrantedUriPermissions;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import java.io.PrintWriter;
@@ -120,6 +122,24 @@ public final class JobStatus {
/** How many times this job has failed, used to compute back-off. */
private final int numFailures;
+ /**
+ * Current standby heartbeat when this job was scheduled or last ran. Used to
+ * pin the runnability check regardless of the job's app moving between buckets.
+ */
+ private final long baseHeartbeat;
+
+ /**
+ * Which app standby bucket this job's app is in. Updated when the app is moved to a
+ * different bucket.
+ */
+ private int standbyBucket;
+
+ /**
+ * Debugging: timestamp if we ever defer this job based on standby bucketing, this
+ * is when we did so.
+ */
+ private long whenStandbyDeferred;
+
// Constraints.
final int requiredConstraints;
int satisfiedConstraints = 0;
@@ -221,10 +241,13 @@ public final class JobStatus {
}
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
- int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis,
- long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime) {
+ int sourceUserId, int standbyBucket, long heartbeat, String tag, int numFailures,
+ long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
+ long lastSuccessfulRunTime, long lastFailedRunTime) {
this.job = job;
this.callingUid = callingUid;
+ this.standbyBucket = standbyBucket;
+ this.baseHeartbeat = heartbeat;
int tempSourceUid = -1;
if (sourceUserId != -1 && sourcePackageName != null) {
@@ -283,6 +306,7 @@ public final class JobStatus {
public JobStatus(JobStatus jobStatus) {
this(jobStatus.getJob(), jobStatus.getUid(),
jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
+ jobStatus.getStandbyBucket(), jobStatus.getBaseHeartbeat(),
jobStatus.getSourceTag(), jobStatus.getNumFailures(),
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime());
@@ -299,13 +323,17 @@ public final class JobStatus {
* {@link android.app.job.JobInfo} time criteria because we can load a persisted periodic job
* from the {@link com.android.server.job.JobStore} and still want to respect its
* wallclock runtime rather than resetting it on every boot.
- * We consider a freshly loaded job to no longer be in back-off.
+ * We consider a freshly loaded job to no longer be in back-off, and the associated
+ * standby bucket is whatever the OS thinks it should be at this moment.
*/
- public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId,
- String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
+ public JobStatus(JobInfo job, int callingUid, String sourcePkgName, int sourceUserId,
+ int standbyBucket, long baseHeartbeat, String sourceTag,
+ long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime,
Pair<Long, Long> persistedExecutionTimesUTC) {
- this(job, callingUid, sourcePackageName, sourceUserId, sourceTag, 0,
+ this(job, callingUid, sourcePkgName, sourceUserId,
+ standbyBucket, baseHeartbeat,
+ sourceTag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime);
@@ -322,11 +350,13 @@ public final class JobStatus {
}
/** Create a new job to be rescheduled with the provided parameters. */
- public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis,
+ public JobStatus(JobStatus rescheduling, long newBaseHeartbeat,
+ long newEarliestRuntimeElapsedMillis,
long newLatestRuntimeElapsedMillis, int backoffAttempt,
long lastSuccessfulRunTime, long lastFailedRunTime) {
this(rescheduling.job, rescheduling.getUid(),
rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
+ rescheduling.getStandbyBucket(), newBaseHeartbeat,
rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
newLatestRuntimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime);
@@ -335,11 +365,12 @@ public final class JobStatus {
/**
* Create a newly scheduled job.
* @param callingUid Uid of the package that scheduled this job.
- * @param sourcePackageName Package name on whose behalf this job is scheduled. Null indicates
+ * @param sourcePkg Package name on whose behalf this job is scheduled. Null indicates
* the calling package is the source.
* @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the
+ * caller.
*/
- public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName,
+ public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePkg,
int sourceUserId, String tag) {
final long elapsedNow = sElapsedRealtimeClock.millis();
final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
@@ -352,7 +383,14 @@ public final class JobStatus {
latestRunTimeElapsedMillis = job.hasLateConstraint() ?
elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
}
- return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0,
+ String jobPackage = (sourcePkg != null) ? sourcePkg : job.getService().getPackageName();
+
+ int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
+ sourceUserId, elapsedNow);
+ JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
+ long currentHeartbeat = js != null ? js.currentHeartbeat() : 0;
+ return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
+ standbyBucket, currentHeartbeat, tag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */);
}
@@ -528,6 +566,29 @@ public final class JobStatus {
return UserHandle.getUserId(callingUid);
}
+ public int getStandbyBucket() {
+ return standbyBucket;
+ }
+
+ public long getBaseHeartbeat() {
+ return baseHeartbeat;
+ }
+
+ // Called only by the standby monitoring code
+ public void setStandbyBucket(int newBucket) {
+ standbyBucket = newBucket;
+ }
+
+ // Called only by the standby monitoring code
+ public long getWhenStandbyDeferred() {
+ return whenStandbyDeferred;
+ }
+
+ // Called only by the standby monitoring code
+ public void setWhenStandbyDeferred(long now) {
+ whenStandbyDeferred = now;
+ }
+
public String getSourceTag() {
return sourceTag;
}
@@ -950,6 +1011,19 @@ public final class JobStatus {
}
}
+ // normalized bucket indices, not the AppStandby constants
+ private String bucketName(int bucket) {
+ switch (bucket) {
+ case 0: return "ACTIVE";
+ case 1: return "WORKING_SET";
+ case 2: return "FREQUENT";
+ case 3: return "RARE";
+ case 4: return "NEVER";
+ default:
+ return "Unknown: " + bucket;
+ }
+ }
+
// Dumpsys infrastructure
public void dump(PrintWriter pw, String prefix, boolean full, long elapsedRealtimeMillis) {
pw.print(prefix); UserHandle.formatUid(pw, callingUid);
@@ -1098,6 +1172,8 @@ public final class JobStatus {
dumpJobWorkItem(pw, prefix, executingWork.get(i), i);
}
}
+ pw.print(prefix); pw.print("Standby bucket: ");
+ pw.println(bucketName(standbyBucket));
pw.print(prefix); pw.print("Enqueue time: ");
TimeUtils.formatDuration(enqueueTime, elapsedRealtimeMillis, pw);
pw.println();
diff --git a/com/android/server/location/ContextHubClientBroker.java b/com/android/server/location/ContextHubClientBroker.java
new file mode 100644
index 00000000..41d9feb9
--- /dev/null
+++ b/com/android/server/location/ContextHubClientBroker.java
@@ -0,0 +1,226 @@
+/*
+ * 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.location;
+
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.NanoAppMessage;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle
+ * notification callbacks. This class implements the IContextHubClient object, and the implemented
+ * APIs must be thread-safe.
+ *
+ * @hide
+ */
+public class ContextHubClientBroker extends IContextHubClient.Stub
+ implements IBinder.DeathRecipient {
+ private static final String TAG = "ContextHubClientBroker";
+
+ /*
+ * The context of the service.
+ */
+ private final Context mContext;
+
+ /*
+ * The proxy to talk to the Context Hub HAL.
+ */
+ private final IContexthub mContextHubProxy;
+
+ /*
+ * The manager that registered this client.
+ */
+ private final ContextHubClientManager mClientManager;
+
+ /*
+ * The ID of the hub that this client is attached to.
+ */
+ private final int mAttachedContextHubId;
+
+ /*
+ * The host end point ID of this client.
+ */
+ private final short mHostEndPointId;
+
+ /*
+ * The remote callback interface for this client.
+ */
+ private final IContextHubClientCallback mCallbackInterface;
+
+ /*
+ * false if the connection has been closed by the client, true otherwise.
+ */
+ private final AtomicBoolean mConnectionOpen = new AtomicBoolean(true);
+
+ /* package */ ContextHubClientBroker(
+ Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
+ int contextHubId, short hostEndPointId, IContextHubClientCallback callback) {
+ mContext = context;
+ mContextHubProxy = contextHubProxy;
+ mClientManager = clientManager;
+ mAttachedContextHubId = contextHubId;
+ mHostEndPointId = hostEndPointId;
+ mCallbackInterface = callback;
+ }
+
+ /**
+ * Attaches a death recipient for this client
+ *
+ * @throws RemoteException if the client has already died
+ */
+ /* package */ void attachDeathRecipient() throws RemoteException {
+ mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+ }
+
+ /**
+ * Sends from this client to a nanoapp.
+ *
+ * @param message the message to send
+ * @return the error code of sending the message
+ */
+ @ContextHubTransaction.Result
+ @Override
+ public int sendMessageToNanoApp(NanoAppMessage message) {
+ ContextHubServiceUtil.checkPermissions(mContext);
+
+ int result;
+ if (mConnectionOpen.get()) {
+ ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage(
+ mHostEndPointId, message);
+
+ try {
+ result = mContextHubProxy.sendMessageToHub(mAttachedContextHubId, messageToNanoApp);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
+ + mAttachedContextHubId + ")", e);
+ result = Result.UNKNOWN_FAILURE;
+ }
+ } else {
+ Log.e(TAG, "Failed to send message to nanoapp: client connection is closed");
+ result = Result.UNKNOWN_FAILURE;
+ }
+
+ return ContextHubServiceUtil.toTransactionResult(result);
+ }
+
+ /**
+ * Closes the connection for this client with the service.
+ */
+ @Override
+ public void close() {
+ if (mConnectionOpen.getAndSet(false)) {
+ mClientManager.unregisterClient(mHostEndPointId);
+ }
+ }
+
+ /**
+ * Invoked when the underlying binder of this broker has died at the client process.
+ */
+ public void binderDied() {
+ try {
+ IContextHubClient.Stub.asInterface(this).close();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while closing client on death", e);
+ }
+ }
+
+ /**
+ * @return the ID of the context hub this client is attached to
+ */
+ /* package */ int getAttachedContextHubId() {
+ return mAttachedContextHubId;
+ }
+
+ /**
+ * @return the host endpoint ID of this client
+ */
+ /* package */ short getHostEndPointId() {
+ return mHostEndPointId;
+ }
+
+ /**
+ * Sends a message to the client associated with this object.
+ *
+ * @param message the message that came from a nanoapp
+ */
+ /* package */ void sendMessageToClient(NanoAppMessage message) {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onMessageFromNanoApp(message);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while sending message to client (host endpoint ID = "
+ + mHostEndPointId + ")", e);
+ }
+ }
+ }
+
+ /**
+ * Handles a nanoapp load event.
+ *
+ * @param nanoAppId the ID of the nanoapp that was loaded.
+ */
+ /* package */ void onNanoAppLoaded(long nanoAppId) {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onNanoAppLoaded(nanoAppId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onNanoAppLoaded on client"
+ + " (host endpoint ID = " + mHostEndPointId + ")", e);
+ }
+ }
+ }
+
+ /**
+ * Handles a nanoapp unload event.
+ *
+ * @param nanoAppId the ID of the nanoapp that was unloaded.
+ */
+ /* package */ void onNanoAppUnloaded(long nanoAppId) {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onNanoAppUnloaded(nanoAppId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onNanoAppUnloaded on client"
+ + " (host endpoint ID = " + mHostEndPointId + ")", e);
+ }
+ }
+ }
+
+ /**
+ * Handles a hub reset for this client.
+ */
+ /* package */ void onHubReset() {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onHubReset();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onHubReset on client" +
+ " (host endpoint ID = " + mHostEndPointId + ")", e);
+ }
+ }
+ }
+}
diff --git a/com/android/server/location/ContextHubClientManager.java b/com/android/server/location/ContextHubClientManager.java
new file mode 100644
index 00000000..d58a7460
--- /dev/null
+++ b/com/android/server/location/ContextHubClientManager.java
@@ -0,0 +1,237 @@
+/*
+ * 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.location;
+
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.NanoAppMessage;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * A class that manages registration/unregistration of clients and manages messages to/from clients.
+ *
+ * @hide
+ */
+/* package */ class ContextHubClientManager {
+ private static final String TAG = "ContextHubClientManager";
+
+ /*
+ * The maximum host endpoint ID value that a client can be assigned.
+ */
+ private static final int MAX_CLIENT_ID = 0x7fff;
+
+ /*
+ * Local flag to enable debug logging.
+ */
+ private static final boolean DEBUG_LOG_ENABLED = true;
+
+ /*
+ * The context of the service.
+ */
+ private final Context mContext;
+
+ /*
+ * The proxy to talk to the Context Hub.
+ */
+ private final IContexthub mContextHubProxy;
+
+ /*
+ * A mapping of host endpoint IDs to the ContextHubClientBroker object of registered clients.
+ * A concurrent data structure is used since the registration/unregistration can occur in
+ * multiple threads.
+ */
+ private final ConcurrentHashMap<Short, ContextHubClientBroker> mHostEndPointIdToClientMap =
+ new ConcurrentHashMap<>();
+
+ /*
+ * The next host endpoint ID to start iterating for the next available host endpoint ID.
+ */
+ private int mNextHostEndpointId = 0;
+
+ /* package */ ContextHubClientManager(
+ Context context, IContexthub contextHubProxy) {
+ mContext = context;
+ mContextHubProxy = contextHubProxy;
+ }
+
+ /**
+ * Registers a new client with the service.
+ *
+ * @param clientCallback the callback interface of the client to register
+ * @param contextHubId the ID of the hub this client is attached to
+ *
+ * @return the client interface
+ *
+ * @throws IllegalStateException if max number of clients have already registered
+ */
+ /* package */ IContextHubClient registerClient(
+ IContextHubClientCallback clientCallback, int contextHubId) {
+ ContextHubClientBroker broker = createNewClientBroker(clientCallback, contextHubId);
+
+ try {
+ broker.attachDeathRecipient();
+ } catch (RemoteException e) {
+ // The client process has died, so we close the connection and return null.
+ Log.e(TAG, "Failed to attach death recipient to client");
+ broker.close();
+ return null;
+ }
+
+ Log.d(TAG, "Registered client with host endpoint ID " + broker.getHostEndPointId());
+ return IContextHubClient.Stub.asInterface(broker);
+ }
+
+ /**
+ * Handles a message sent from a nanoapp.
+ *
+ * @param contextHubId the ID of the hub where the nanoapp sent the message from
+ * @param message the message send by a nanoapp
+ */
+ /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) {
+ NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message);
+
+ if (DEBUG_LOG_ENABLED) {
+ String targetAudience = clientMessage.isBroadcastMessage() ? "broadcast" : "unicast";
+ Log.v(TAG, "Received a " + targetAudience + " message from nanoapp 0x"
+ + Long.toHexString(clientMessage.getNanoAppId()));
+ }
+
+ if (clientMessage.isBroadcastMessage()) {
+ broadcastMessage(contextHubId, clientMessage);
+ } else {
+ ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint);
+ if (proxy != null) {
+ proxy.sendMessageToClient(clientMessage);
+ } else {
+ Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = "
+ + message.hostEndPoint + ")");
+ }
+ }
+ }
+
+ /**
+ * Unregisters a client from the service.
+ *
+ * This method should be invoked as a result of a client calling the ContextHubClient.close(),
+ * or if the client process has died.
+ *
+ * @param hostEndPointId the host endpoint ID of the client that has died
+ */
+ /* package */ void unregisterClient(short hostEndPointId) {
+ if (mHostEndPointIdToClientMap.remove(hostEndPointId) != null) {
+ Log.d(TAG, "Unregistered client with host endpoint ID " + hostEndPointId);
+ } else {
+ Log.e(TAG, "Cannot unregister non-existing client with host endpoint ID "
+ + hostEndPointId);
+ }
+ }
+
+ /**
+ * Handles a nanoapp load event.
+ *
+ * @param contextHubId the ID of the hub where the nanoapp was loaded.
+ * @param nanoAppId the ID of the nanoapp that was loaded.
+ */
+ /* package */ void onNanoAppLoaded(int contextHubId, long nanoAppId) {
+ forEachClientOfHub(contextHubId, client -> client.onNanoAppLoaded(nanoAppId));
+ }
+
+ /**
+ * Handles a nanoapp unload event.
+ *
+ * @param contextHubId the ID of the hub where the nanoapp was unloaded.
+ * @param nanoAppId the ID of the nanoapp that was unloaded.
+ */
+ /* package */ void onNanoAppUnloaded(int contextHubId, long nanoAppId) {
+ forEachClientOfHub(contextHubId, client -> client.onNanoAppUnloaded(nanoAppId));
+ }
+
+ /**
+ * Handles a hub reset.
+ *
+ * @param contextHubId the ID of the hub that has reset.
+ */
+ /* package */ void onHubReset(int contextHubId) {
+ forEachClientOfHub(contextHubId, client -> client.onHubReset());
+ }
+
+ /**
+ * Creates a new ContextHubClientBroker object for a client and registers it with the client
+ * manager.
+ *
+ * @param clientCallback the callback interface of the client to register
+ * @param contextHubId the ID of the hub this client is attached to
+ *
+ * @return the ContextHubClientBroker object
+ *
+ * @throws IllegalStateException if max number of clients have already registered
+ */
+ private synchronized ContextHubClientBroker createNewClientBroker(
+ IContextHubClientCallback clientCallback, int contextHubId) {
+ if (mHostEndPointIdToClientMap.size() == MAX_CLIENT_ID + 1) {
+ throw new IllegalStateException("Could not register client - max limit exceeded");
+ }
+
+ ContextHubClientBroker broker = null;
+ int id = mNextHostEndpointId;
+ for (int i = 0; i <= MAX_CLIENT_ID; i++) {
+ if (!mHostEndPointIdToClientMap.containsKey(id)) {
+ broker = new ContextHubClientBroker(
+ mContext, mContextHubProxy, this, contextHubId, (short)id, clientCallback);
+ mHostEndPointIdToClientMap.put((short)id, broker);
+ mNextHostEndpointId = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+ break;
+ }
+
+ id = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+ }
+
+ return broker;
+ }
+
+ /**
+ * Broadcasts a message from a nanoapp to all clients attached to the associated hub.
+ *
+ * @param contextHubId the ID of the hub where the nanoapp sent the message from
+ * @param message the message send by a nanoapp
+ */
+ private void broadcastMessage(int contextHubId, NanoAppMessage message) {
+ forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message));
+ }
+
+ /**
+ * Runs a command for each client that is attached to a hub with the given ID.
+ *
+ * @param contextHubId the ID of the hub
+ * @param callback the command to invoke for the client
+ */
+ private void forEachClientOfHub(int contextHubId, Consumer<ContextHubClientBroker> callback) {
+ for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
+ if (broker.getAttachedContextHubId() == contextHubId) {
+ callback.accept(broker);
+ }
+ }
+ }
+}
diff --git a/com/android/server/location/ContextHubService.java b/com/android/server/location/ContextHubService.java
index 5e9f3550..e08c6596 100644
--- a/com/android/server/location/ContextHubService.java
+++ b/com/android/server/location/ContextHubService.java
@@ -16,17 +16,29 @@
package com.android.server.location;
-import android.Manifest;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.hardware.contexthub.V1_0.AsyncEventType;
+import android.hardware.contexthub.V1_0.ContextHub;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.HubAppInfo;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.IContexthubCallback;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.contexthub.V1_0.TransactionResult;
import android.hardware.location.ContextHubInfo;
-import android.hardware.location.ContextHubManager;
import android.hardware.location.ContextHubMessage;
-import android.hardware.location.IContextHubService;
+import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubCallback;
-import android.hardware.location.NanoAppFilter;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.IContextHubService;
+import android.hardware.location.IContextHubTransactionCallback;
import android.hardware.location.NanoApp;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppFilter;
import android.hardware.location.NanoAppInstanceInfo;
+import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
@@ -38,65 +50,230 @@ import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
/**
* @hide
*/
public class ContextHubService extends IContextHubService.Stub {
private static final String TAG = "ContextHubService";
- private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
- private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
- + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
- public static final int ANY_HUB = -1;
- public static final int MSG_LOAD_NANO_APP = 3;
+ /*
+ * Constants for the type of transaction that is defined by ContextHubService.
+ * This is used to report the transaction callback to clients, and is different from
+ * ContextHubTransaction.Type.
+ */
+ public static final int MSG_ENABLE_NANO_APP = 1;
+ public static final int MSG_DISABLE_NANO_APP = 2;
+ public static final int MSG_LOAD_NANO_APP = 3;
public static final int MSG_UNLOAD_NANO_APP = 4;
+ public static final int MSG_QUERY_NANO_APPS = 5;
+ public static final int MSG_QUERY_MEMORY = 6;
+ public static final int MSG_HUB_RESET = 7;
private static final String PRE_LOADED_GENERIC_UNKNOWN = "Preloaded app, unknown";
private static final String PRE_LOADED_APP_NAME = PRE_LOADED_GENERIC_UNKNOWN;
private static final String PRE_LOADED_APP_PUBLISHER = PRE_LOADED_GENERIC_UNKNOWN;
private static final int PRE_LOADED_APP_MEM_REQ = 0;
- private static final int MSG_HEADER_SIZE = 4;
- private static final int HEADER_FIELD_MSG_TYPE = 0;
- private static final int HEADER_FIELD_MSG_VERSION = 1;
- private static final int HEADER_FIELD_HUB_HANDLE = 2;
- private static final int HEADER_FIELD_APP_INSTANCE = 3;
-
- private static final int HEADER_FIELD_LOAD_APP_ID_LO = MSG_HEADER_SIZE;
- private static final int HEADER_FIELD_LOAD_APP_ID_HI = MSG_HEADER_SIZE + 1;
- private static final int MSG_LOAD_APP_HEADER_SIZE = MSG_HEADER_SIZE + 2;
-
private static final int OS_APP_INSTANCE = -1;
private final Context mContext;
+
+ // TODO(b/69270990): Remove once old ContextHubManager API is deprecated
+ // Service cache maintaining of instance ID to nanoapp infos
private final ConcurrentHashMap<Integer, NanoAppInstanceInfo> mNanoAppHash =
new ConcurrentHashMap<>();
+ // The next available instance ID (managed by the service) to assign to a nanoapp
+ private int mNextAvailableInstanceId = 0;
+ // A map of the long nanoapp ID to instance ID managed by the service
+ private final ConcurrentHashMap<Long, Integer> mNanoAppIdToInstanceMap =
+ new ConcurrentHashMap<>();
+
private final ContextHubInfo[] mContextHubInfo;
private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
new RemoteCallbackList<>();
- private native int nativeSendMessage(int[] header, byte[] data);
- private native ContextHubInfo[] nativeInitialize();
+ // Proxy object to communicate with the Context Hub HAL
+ private final IContexthub mContextHubProxy;
+
+ // The manager for transaction queue
+ private final ContextHubTransactionManager mTransactionManager;
+
+ // The manager for sending messages to/from clients
+ private final ContextHubClientManager mClientManager;
+
+ // The default client for old API clients
+ private final Map<Integer, IContextHubClient> mDefaultClientMap;
+
+ /**
+ * Class extending the callback to register with a Context Hub.
+ */
+ private class ContextHubServiceCallback extends IContexthubCallback.Stub {
+ private final int mContextHubId;
+
+ ContextHubServiceCallback(int contextHubId) {
+ mContextHubId = contextHubId;
+ }
+
+ @Override
+ public void handleClientMsg(ContextHubMsg message) {
+ handleClientMessageCallback(mContextHubId, message);
+ }
+
+ @Override
+ public void handleTxnResult(int transactionId, int result) {
+ handleTransactionResultCallback(mContextHubId, transactionId, result);
+ }
+
+ @Override
+ public void handleHubEvent(int eventType) {
+ handleHubEventCallback(mContextHubId, eventType);
+ }
+
+ @Override
+ public void handleAppAbort(long nanoAppId, int abortCode) {
+ handleAppAbortCallback(mContextHubId, nanoAppId, abortCode);
+ }
+
+ @Override
+ public void handleAppsInfo(ArrayList<HubAppInfo> nanoAppInfoList) {
+ handleQueryAppsCallback(mContextHubId, nanoAppInfoList);
+ }
+ }
public ContextHubService(Context context) {
mContext = context;
- mContextHubInfo = nativeInitialize();
+
+ mContextHubProxy = getContextHubProxy();
+ if (mContextHubProxy == null) {
+ mTransactionManager = null;
+ mClientManager = null;
+ mDefaultClientMap = Collections.EMPTY_MAP;
+ mContextHubInfo = new ContextHubInfo[0];
+ return;
+ }
+
+ mClientManager = new ContextHubClientManager(mContext, mContextHubProxy);
+ mTransactionManager = new ContextHubTransactionManager(mContextHubProxy, mClientManager);
+
+ List<ContextHub> hubList;
+ try {
+ hubList = mContextHubProxy.getHubs();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while getting Context Hub info", e);
+ hubList = Collections.emptyList();
+ }
+ mContextHubInfo = ContextHubServiceUtil.createContextHubInfoArray(hubList);
+
+ HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
+ for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+ int contextHubId = contextHubInfo.getId();
+
+ IContextHubClient client = mClientManager.registerClient(
+ createDefaultClientCallback(contextHubId), contextHubId);
+ defaultClientMap.put(contextHubId, client);
+ }
+ mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
+
+ for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+ int contextHubId = contextHubInfo.getId();
+ try {
+ mContextHubProxy.registerCallback(
+ contextHubId, new ContextHubServiceCallback(contextHubId));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while registering service callback for hub (ID = "
+ + contextHubId + ")", e);
+ }
+ }
+
+ // Do a query to initialize the service cache list of nanoapps
+ // TODO(b/69270990): Remove this when old API is deprecated
+ for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+ queryNanoAppsInternal(contextHubInfo.getId());
+ }
for (int i = 0; i < mContextHubInfo.length; i++) {
Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId()
- + ", name: " + mContextHubInfo[i].getName());
+ + ", name: " + mContextHubInfo[i].getName());
}
}
+ /**
+ * Creates a default client callback for old API clients.
+ *
+ * @param contextHubId the ID of the hub to attach this client to
+ * @return the internal callback interface
+ */
+ private IContextHubClientCallback createDefaultClientCallback(int contextHubId) {
+ return new IContextHubClientCallback.Stub() {
+ @Override
+ public void onMessageFromNanoApp(NanoAppMessage message) {
+ int nanoAppInstanceId =
+ mNanoAppIdToInstanceMap.containsKey(message.getNanoAppId()) ?
+ mNanoAppIdToInstanceMap.get(message.getNanoAppId()) : -1;
+
+ onMessageReceiptOldApi(
+ message.getMessageType(), contextHubId, nanoAppInstanceId,
+ message.getMessageBody());
+ }
+
+ @Override
+ public void onHubReset() {
+ byte[] data = {TransactionResult.SUCCESS};
+ onMessageReceiptOldApi(MSG_HUB_RESET, contextHubId, OS_APP_INSTANCE, data);
+ }
+
+ @Override
+ public void onNanoAppAborted(long nanoAppId, int abortCode) {
+ }
+
+ @Override
+ public void onNanoAppLoaded(long nanoAppId) {
+ }
+
+ @Override
+ public void onNanoAppUnloaded(long nanoAppId) {
+ }
+
+ @Override
+ public void onNanoAppEnabled(long nanoAppId) {
+ }
+
+ @Override
+ public void onNanoAppDisabled(long nanoAppId) {
+ }
+ };
+ }
+
+ /**
+ * @return the IContexthub proxy interface
+ */
+ private IContexthub getContextHubProxy() {
+ IContexthub proxy = null;
+ try {
+ proxy = IContexthub.getService(true /* retry */);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy", e);
+ } catch (NoSuchElementException e) {
+ Log.i(TAG, "Context Hub HAL service not found");
+ }
+
+ return proxy;
+ }
+
@Override
public int registerCallback(IContextHubCallback callback) throws RemoteException {
checkPermissions();
mCallbacksList.register(callback);
+
Log.d(TAG, "Added callback, total callbacks " +
- mCallbacksList.getRegisteredCallbackCount());
+ mCallbacksList.getRegisteredCallbackCount());
return 0;
}
@@ -109,29 +286,112 @@ public class ContextHubService extends IContextHubService.Stub {
for (int i = 0; i < returnArray.length; ++i) {
returnArray[i] = i;
Log.d(TAG, String.format("Hub %s is mapped to %d",
- mContextHubInfo[i].getName(), returnArray[i]));
+ mContextHubInfo[i].getName(), returnArray[i]));
}
return returnArray;
}
@Override
- public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
+ public ContextHubInfo getContextHubInfo(int contextHubId) throws RemoteException {
checkPermissions();
- if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) {
- Log.e(TAG, "Invalid context hub handle " + contextHubHandle);
+ if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
+ Log.e(TAG, "Invalid context hub handle " + contextHubId);
return null; // null means fail
}
- return mContextHubInfo[contextHubHandle];
+ return mContextHubInfo[contextHubId];
+ }
+
+ /**
+ * Creates an internal load transaction callback to be used for old API clients
+ *
+ * @param contextHubId the ID of the hub to load the binary
+ * @param nanoAppBinary the binary to load
+ * @return the callback interface
+ */
+ private IContextHubTransactionCallback createLoadTransactionCallback(
+ int contextHubId, NanoAppBinary nanoAppBinary) {
+ return new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onTransactionComplete(int result) {
+ handleLoadResponseOldApi(contextHubId, result, nanoAppBinary);
+ }
+
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ }
+ };
+ }
+
+ /**
+ * Creates an internal unload transaction callback to be used for old API clients
+ *
+ * @param contextHubId the ID of the hub to unload the nanoapp
+ * @param nanoAppId the ID of the nanoapp to unload
+ * @return the callback interface
+ */
+ private IContextHubTransactionCallback createUnloadTransactionCallback(
+ int contextHubId, long nanoAppId) {
+ return new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onTransactionComplete(int result) {
+ handleUnloadResponseOldApi(contextHubId, result, nanoAppId);
+ }
+
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ }
+ };
+ }
+
+ /**
+ * Creates an internal query transaction callback to be used for old API clients
+ *
+ * @param contextHubId the ID of the hub to query
+ * @return the callback interface
+ */
+ private IContextHubTransactionCallback createQueryTransactionCallback(int contextHubId) {
+ return new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onTransactionComplete(int result) {
+ }
+
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ byte[] data = {(byte) result};
+ onMessageReceiptOldApi(MSG_QUERY_NANO_APPS, contextHubId, OS_APP_INSTANCE, data);
+ }
+ };
+ }
+
+ /**
+ * Adds a new transaction to the transaction manager queue
+ *
+ * @param transaction the transaction to add
+ * @return the result of adding the transaction
+ */
+ private int addTransaction(ContextHubServiceTransaction transaction) {
+ int result = Result.OK;
+ try {
+ mTransactionManager.addTransaction(transaction);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, e.getMessage());
+ result = Result.TRANSACTION_PENDING; /* failed */
+ }
+
+ return result;
}
@Override
- public int loadNanoApp(int contextHubHandle, NanoApp app) throws RemoteException {
+ public int loadNanoApp(int contextHubId, NanoApp app) throws RemoteException {
checkPermissions();
+ if (mContextHubProxy == null) {
+ return -1;
+ }
- if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) {
- Log.e(TAG, "Invalid contextHubhandle " + contextHubHandle);
+ if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
+ Log.e(TAG, "Invalid contextHubhandle " + contextHubId);
return -1;
}
if (app == null) {
@@ -139,20 +399,17 @@ public class ContextHubService extends IContextHubService.Stub {
return -1;
}
- int[] msgHeader = new int[MSG_LOAD_APP_HEADER_SIZE];
- msgHeader[HEADER_FIELD_HUB_HANDLE] = contextHubHandle;
- msgHeader[HEADER_FIELD_APP_INSTANCE] = OS_APP_INSTANCE;
- msgHeader[HEADER_FIELD_MSG_VERSION] = 0;
- msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_LOAD_NANO_APP;
-
- long appId = app.getAppId();
+ // Create an internal IContextHubTransactionCallback for the old API clients
+ NanoAppBinary nanoAppBinary = new NanoAppBinary(app.getAppBinary());
+ IContextHubTransactionCallback onCompleteCallback =
+ createLoadTransactionCallback(contextHubId, nanoAppBinary);
- msgHeader[HEADER_FIELD_LOAD_APP_ID_LO] = (int)(appId & 0xFFFFFFFF);
- msgHeader[HEADER_FIELD_LOAD_APP_ID_HI] = (int)((appId >> 32) & 0xFFFFFFFF);
+ ContextHubServiceTransaction transaction = mTransactionManager.createLoadTransaction(
+ contextHubId, nanoAppBinary, onCompleteCallback);
- int errVal = nativeSendMessage(msgHeader, app.getAppBinary());
- if (errVal != 0) {
- Log.e(TAG, "Send Message returns error" + contextHubHandle);
+ int result = addTransaction(transaction);
+ if (result != Result.OK) {
+ Log.e(TAG, "Failed to load nanoapp with error code " + result);
return -1;
}
@@ -163,23 +420,26 @@ public class ContextHubService extends IContextHubService.Stub {
@Override
public int unloadNanoApp(int nanoAppInstanceHandle) throws RemoteException {
checkPermissions();
+ if (mContextHubProxy == null) {
+ return -1;
+ }
+
NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstanceHandle);
if (info == null) {
Log.e(TAG, "Cannot find app with handle " + nanoAppInstanceHandle);
return -1; //means failed
}
- // Call Native interface here
- int[] msgHeader = new int[MSG_HEADER_SIZE];
- msgHeader[HEADER_FIELD_HUB_HANDLE] = ANY_HUB;
- msgHeader[HEADER_FIELD_APP_INSTANCE] = nanoAppInstanceHandle;
- msgHeader[HEADER_FIELD_MSG_VERSION] = 0;
- msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_UNLOAD_NANO_APP;
+ int contextHubId = info.getContexthubId();
+ long nanoAppId = info.getAppId();
+ IContextHubTransactionCallback onCompleteCallback =
+ createUnloadTransactionCallback(contextHubId, nanoAppId);
+ ContextHubServiceTransaction transaction = mTransactionManager.createUnloadTransaction(
+ contextHubId, nanoAppId, onCompleteCallback);
- byte msg[] = new byte[0];
-
- if (nativeSendMessage(msgHeader, msg) != 0) {
- Log.e(TAG, "native send message fails");
+ int result = addTransaction(transaction);
+ if (result != Result.OK) {
+ Log.e(TAG, "Failed to unload nanoapp with error code " + result);
return -1;
}
@@ -189,7 +449,7 @@ public class ContextHubService extends IContextHubService.Stub {
@Override
public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppInstanceHandle)
- throws RemoteException {
+ throws RemoteException {
checkPermissions();
// This assumes that all the nanoAppInfo is current. This is reasonable
// for the use cases for tightly controlled nanoApps.
@@ -206,7 +466,7 @@ public class ContextHubService extends IContextHubService.Stub {
checkPermissions();
ArrayList<Integer> foundInstances = new ArrayList<Integer>();
- for (Integer nanoAppInstance: mNanoAppHash.keySet()) {
+ for (Integer nanoAppInstance : mNanoAppHash.keySet()) {
NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstance);
if (filter.testMatch(info)) {
@@ -223,23 +483,259 @@ public class ContextHubService extends IContextHubService.Stub {
return retArray;
}
+ /**
+ * Performs a query at the specified hub.
+ *
+ * This method should only be invoked internally by the service, either to update the service
+ * cache or as a result of an explicit query requested by a client through the sendMessage API.
+ *
+ * @param contextHubId the ID of the hub to do the query
+ * @return the result of the query
+ */
+ private int queryNanoAppsInternal(int contextHubId) {
+ if (mContextHubProxy == null) {
+ return Result.UNKNOWN_FAILURE;
+ }
+
+ IContextHubTransactionCallback onCompleteCallback =
+ createQueryTransactionCallback(contextHubId);
+ ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+ contextHubId, onCompleteCallback);
+
+ return addTransaction(transaction);
+ }
+
@Override
- public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage msg)
- throws RemoteException {
+ public int sendMessage(
+ int hubHandle, int nanoAppHandle, ContextHubMessage msg) throws RemoteException {
checkPermissions();
-
- if (msg == null || msg.getData() == null) {
- Log.w(TAG, "null ptr");
+ if (mContextHubProxy == null) {
+ return -1;
+ }
+ if (msg == null) {
+ Log.e(TAG, "ContextHubMessage cannot be null");
+ return -1;
+ }
+ if (msg.getData() == null) {
+ Log.e(TAG, "ContextHubMessage message body cannot be null");
+ return -1;
+ }
+ if (!mDefaultClientMap.containsKey(hubHandle)) {
+ Log.e(TAG, "Hub with ID " + hubHandle + " does not exist");
return -1;
}
- int[] msgHeader = new int[MSG_HEADER_SIZE];
- msgHeader[HEADER_FIELD_HUB_HANDLE] = hubHandle;
- msgHeader[HEADER_FIELD_APP_INSTANCE] = nanoAppHandle;
- msgHeader[HEADER_FIELD_MSG_VERSION] = msg.getVersion();
- msgHeader[HEADER_FIELD_MSG_TYPE] = msg.getMsgType();
+ boolean success = false;
+ if (nanoAppHandle == OS_APP_INSTANCE) {
+ if (msg.getMsgType() == MSG_QUERY_NANO_APPS) {
+ success = (queryNanoAppsInternal(hubHandle) == Result.OK);
+ } else {
+ Log.e(TAG, "Invalid OS message params of type " + msg.getMsgType());
+ }
+ } else {
+ NanoAppInstanceInfo info = getNanoAppInstanceInfo(nanoAppHandle);
+ if (info != null) {
+ NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(
+ info.getAppId(), msg.getMsgType(), msg.getData());
+
+ IContextHubClient client = mDefaultClientMap.get(hubHandle);
+ success = (client.sendMessageToNanoApp(message) ==
+ ContextHubTransaction.TRANSACTION_SUCCESS);
+ } else {
+ Log.e(TAG, "Failed to send nanoapp message - nanoapp with instance ID "
+ + nanoAppHandle + " does not exist.");
+ }
+ }
+
+ return success ? 0 : -1;
+ }
- return nativeSendMessage(msgHeader, msg.getData());
+ /**
+ * Handles a unicast or broadcast message from a nanoapp.
+ *
+ * @param contextHubId the ID of the hub the message came from
+ * @param message the message contents
+ */
+ private void handleClientMessageCallback(int contextHubId, ContextHubMsg message) {
+ mClientManager.onMessageFromNanoApp(contextHubId, message);
+ }
+
+ /**
+ * A helper function to handle a load response from the Context Hub for the old API.
+ *
+ * TODO(b/69270990): Remove this once the old APIs are obsolete.
+ */
+ private void handleLoadResponseOldApi(
+ int contextHubId, int result, NanoAppBinary nanoAppBinary) {
+ if (nanoAppBinary == null) {
+ Log.e(TAG, "Nanoapp binary field was null for a load transaction");
+ return;
+ }
+
+ // NOTE: The legacy JNI code used to do a query right after a load success
+ // to synchronize the service cache. Instead store the binary that was requested to
+ // load to update the cache later without doing a query.
+ int instanceId = 0;
+ long nanoAppId = nanoAppBinary.getNanoAppId();
+ int nanoAppVersion = nanoAppBinary.getNanoAppVersion();
+ if (result == TransactionResult.SUCCESS) {
+ if (mNanoAppIdToInstanceMap.containsKey(nanoAppId)) {
+ instanceId = mNanoAppIdToInstanceMap.get(nanoAppId);
+ } else {
+ instanceId = mNextAvailableInstanceId++;
+ mNanoAppIdToInstanceMap.put(nanoAppId, instanceId);
+ }
+
+ addAppInstance(contextHubId, instanceId, nanoAppId, nanoAppVersion);
+ }
+
+ byte[] data = new byte[5];
+ data[0] = (byte) result;
+ ByteBuffer.wrap(data, 1, 4).order(ByteOrder.nativeOrder()).putInt(instanceId);
+
+ onMessageReceiptOldApi(MSG_LOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
+ }
+
+ /**
+ * A helper function to handle an unload response from the Context Hub for the old API.
+ *
+ * TODO(b/69270990): Remove this once the old APIs are obsolete.
+ */
+ private void handleUnloadResponseOldApi(
+ int contextHubId, int result, long nanoAppId) {
+ if (result == TransactionResult.SUCCESS) {
+ int instanceId = mNanoAppIdToInstanceMap.get(nanoAppId);
+ deleteAppInstance(instanceId);
+ mNanoAppIdToInstanceMap.remove(nanoAppId);
+ }
+
+ byte[] data = new byte[1];
+ data[0] = (byte) result;
+ onMessageReceiptOldApi(MSG_UNLOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
+ }
+
+ /**
+ * Handles a transaction response from a Context Hub.
+ *
+ * @param contextHubId the ID of the hub the response came from
+ * @param transactionId the ID of the transaction
+ * @param result the result of the transaction reported by the hub
+ */
+ private void handleTransactionResultCallback(int contextHubId, int transactionId, int result) {
+ mTransactionManager.onTransactionResponse(transactionId, result);
+ }
+
+ /**
+ * Handles an asynchronous event from a Context Hub.
+ *
+ * @param contextHubId the ID of the hub the response came from
+ * @param eventType the type of the event as defined in Context Hub HAL AsyncEventType
+ */
+ private void handleHubEventCallback(int contextHubId, int eventType) {
+ if (eventType == AsyncEventType.RESTARTED) {
+ mTransactionManager.onHubReset();
+ queryNanoAppsInternal(contextHubId);
+
+ mClientManager.onHubReset(contextHubId);
+ } else {
+ Log.i(TAG, "Received unknown hub event (hub ID = " + contextHubId + ", type = "
+ + eventType + ")");
+ }
+ }
+
+ /**
+ * Handles an asynchronous abort event of a nanoapp.
+ *
+ * @param contextHubId the ID of the hub that the nanoapp aborted in
+ * @param nanoAppId the ID of the aborted nanoapp
+ * @param abortCode the nanoapp-specific abort code
+ */
+ private void handleAppAbortCallback(int contextHubId, long nanoAppId, int abortCode) {
+ // TODO(b/31049861): Implement this
+ }
+
+ /**
+ * Handles a query response from a Context Hub.
+ *
+ * @param contextHubId the ID of the hub of the response
+ * @param nanoAppInfoList the list of loaded nanoapps
+ */
+ private void handleQueryAppsCallback(int contextHubId, List<HubAppInfo> nanoAppInfoList) {
+ List<NanoAppState> nanoAppStateList =
+ ContextHubServiceUtil.createNanoAppStateList(nanoAppInfoList);
+
+ updateServiceCache(contextHubId, nanoAppInfoList);
+ mTransactionManager.onQueryResponse(nanoAppStateList);
+ }
+
+ /**
+ * Updates the service's cache of the list of loaded nanoapps using a nanoapp list response.
+ *
+ * TODO(b/69270990): Remove this when the old API functionality is removed.
+ *
+ * @param contextHubId the ID of the hub the response came from
+ * @param nanoAppInfoList the list of loaded nanoapps
+ */
+ private void updateServiceCache(int contextHubId, List<HubAppInfo> nanoAppInfoList) {
+ synchronized (mNanoAppHash) {
+ for (int instanceId : mNanoAppHash.keySet()) {
+ if (mNanoAppHash.get(instanceId).getContexthubId() == contextHubId) {
+ deleteAppInstance(instanceId);
+ }
+ }
+
+ for (HubAppInfo appInfo : nanoAppInfoList) {
+ int instanceId;
+ long nanoAppId = appInfo.appId;
+ if (mNanoAppIdToInstanceMap.containsKey(nanoAppId)) {
+ instanceId = mNanoAppIdToInstanceMap.get(nanoAppId);
+ } else {
+ instanceId = mNextAvailableInstanceId++;
+ mNanoAppIdToInstanceMap.put(nanoAppId, instanceId);
+ }
+
+ addAppInstance(contextHubId, instanceId, nanoAppId, appInfo.version);
+ }
+ }
+ }
+
+ /**
+ * @param contextHubId the hub ID to validate
+ * @return {@code true} if the ID represents that of an available hub, {@code false} otherwise
+ */
+ private boolean isValidContextHubId(int contextHubId) {
+ for (ContextHubInfo hubInfo : mContextHubInfo) {
+ if (hubInfo.getId() == contextHubId) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates and registers a client at the service for the specified Context Hub.
+ *
+ * @param clientCallback the client interface to register with the service
+ * @param contextHubId the ID of the hub this client is attached to
+ * @return the generated client interface, null if registration was unsuccessful
+ *
+ * @throws IllegalArgumentException if contextHubId is not a valid ID
+ * @throws IllegalStateException if max number of clients have already registered
+ * @throws NullPointerException if clientCallback is null
+ */
+ @Override
+ public IContextHubClient createClient(
+ IContextHubClientCallback clientCallback, int contextHubId) throws RemoteException {
+ checkPermissions();
+ if (!isValidContextHubId(contextHubId)) {
+ throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
+ }
+ if (clientCallback == null) {
+ throw new NullPointerException("Cannot register client with null callback");
+ }
+
+ return mClientManager.registerClient(clientCallback, contextHubId);
}
@Override
@@ -257,7 +753,7 @@ public class ContextHubService extends IContextHubService.Stub {
pw.println("");
pw.println("=================== NANOAPPS ====================");
// Dump nanoAppHash
- for (Integer nanoAppInstance: mNanoAppHash.keySet()) {
+ for (Integer nanoAppInstance : mNanoAppHash.keySet()) {
pw.println(nanoAppInstance + " : " + mNanoAppHash.get(nanoAppInstance).toString());
}
@@ -265,22 +761,18 @@ public class ContextHubService extends IContextHubService.Stub {
}
private void checkPermissions() {
- mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+ ContextHubServiceUtil.checkPermissions(mContext);
}
- private int onMessageReceipt(int[] header, byte[] data) {
- if (header == null || data == null || header.length < MSG_HEADER_SIZE) {
- return -1;
+ private int onMessageReceiptOldApi(int msgType, int hubHandle, int appInstance, byte[] data) {
+ if (data == null) {
+ return -1;
}
+ int msgVersion = 0;
int callbacksCount = mCallbacksList.beginBroadcast();
- int msgType = header[HEADER_FIELD_MSG_TYPE];
- int msgVersion = header[HEADER_FIELD_MSG_VERSION];
- int hubHandle = header[HEADER_FIELD_HUB_HANDLE];
- int appInstance = header[HEADER_FIELD_APP_INSTANCE];
-
Log.d(TAG, "Sending message " + msgType + " version " + msgVersion + " from hubHandle " +
- hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount);
+ hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount);
if (callbacksCount < 1) {
Log.v(TAG, "No message callbacks registered.");
@@ -323,8 +815,8 @@ public class ContextHubService extends IContextHubService.Stub {
}
mNanoAppHash.put(appInstanceHandle, appInfo);
- Log.d(TAG, action + " app instance " + appInstanceHandle + " with id "
- + appId + " version " + appVersion);
+ Log.d(TAG, action + " app instance " + appInstanceHandle + " with id 0x"
+ + Long.toHexString(appId) + " version 0x" + Integer.toHexString(appVersion));
return 0;
}
diff --git a/com/android/server/location/ContextHubServiceTransaction.java b/com/android/server/location/ContextHubServiceTransaction.java
new file mode 100644
index 00000000..66145bbb
--- /dev/null
+++ b/com/android/server/location/ContextHubServiceTransaction.java
@@ -0,0 +1,161 @@
+/*
+ * 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.location;
+
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.NanoAppState;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An abstract class representing transactions requested to the Context Hub Service.
+ *
+ * @hide
+ */
+/* package */ abstract class ContextHubServiceTransaction {
+ private final int mTransactionId;
+ @ContextHubTransaction.Type
+ private final int mTransactionType;
+
+ /*
+ * true if the transaction has already completed, false otherwise
+ */
+ private boolean mIsComplete = false;
+
+ /* package */ ContextHubServiceTransaction(int id, int type) {
+ mTransactionId = id;
+ mTransactionType = type;
+ }
+
+ /**
+ * Starts this transaction with a Context Hub.
+ *
+ * All instances of this class must implement this method by making an asynchronous request to
+ * a hub.
+ *
+ * @return the synchronous error code of the transaction start
+ */
+ /* package */
+ abstract int onTransact();
+
+ /**
+ * A function to invoke when a transaction times out.
+ *
+ * All instances of this class must implement this method by reporting the timeout to the
+ * client.
+ */
+ /* package */
+ abstract void onTimeout();
+
+ /**
+ * A function to invoke when the transaction completes.
+ *
+ * Only relevant for load, unload, enable, or disable transactions.
+ *
+ * @param result the result of the transaction
+ */
+ /* package */ void onTransactionComplete(int result) {
+ }
+
+ /**
+ * A function to invoke when a query transaction completes.
+ *
+ * Only relevant for query transactions.
+ *
+ * @param result the result of the query
+ * @param nanoAppStateList the list of nanoapps given by the query response
+ */
+ /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ }
+
+ /**
+ * @return the ID of this transaction
+ */
+ /* package */ int getTransactionId() {
+ return mTransactionId;
+ }
+
+ /**
+ * @return the type of this transaction
+ * @see ContextHubTransaction.Type
+ */
+ @ContextHubTransaction.Type
+ /* package */ int getTransactionType() {
+ return mTransactionType;
+ }
+
+ /**
+ * Gets the timeout period as defined in IContexthub.hal
+ *
+ * @return the timeout of this transaction in the specified time unit
+ */
+ /* package */ long getTimeout(TimeUnit unit) {
+ switch (mTransactionType) {
+ case ContextHubTransaction.TYPE_LOAD_NANOAPP:
+ return unit.convert(30L, TimeUnit.SECONDS);
+ case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
+ case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
+ case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
+ case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
+ // Note: query timeout is not specified at the HAL
+ default: /* fall through */
+ return unit.convert(5L, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Marks the transaction as complete.
+ *
+ * Should only be called as a result of a response from a Context Hub callback
+ */
+ /* package */ void setComplete() {
+ mIsComplete = true;
+ }
+
+ /**
+ * @return true if the transaction has already completed, false otherwise
+ */
+ /* package */ boolean isComplete() {
+ return mIsComplete;
+ }
+
+ /**
+ * @return the human-readable string of this transaction's type
+ */
+ private String getTransactionTypeString() {
+ switch (mTransactionType) {
+ case ContextHubTransaction.TYPE_LOAD_NANOAPP:
+ return "Load";
+ case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
+ return "Unload";
+ case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
+ return "Enable";
+ case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
+ return "Disable";
+ case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
+ return "Query";
+ default:
+ return "Unknown";
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getTransactionTypeString() + " transaction (ID = " + mTransactionId + ")";
+ }
+}
diff --git a/com/android/server/location/ContextHubServiceUtil.java b/com/android/server/location/ContextHubServiceUtil.java
new file mode 100644
index 00000000..6faeb72e
--- /dev/null
+++ b/com/android/server/location/ContextHubServiceUtil.java
@@ -0,0 +1,211 @@
+/*
+ * 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.location;
+
+import android.Manifest;
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHub;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.HostEndPoint;
+import android.hardware.contexthub.V1_0.HubAppInfo;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
+import android.util.Log;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * A class encapsulating helper functions used by the ContextHubService class
+ */
+/* package */ class ContextHubServiceUtil {
+ private static final String TAG = "ContextHubServiceUtil";
+ private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
+ private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
+ + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
+
+ /**
+ * Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects.
+ *
+ * @param hubList the ContextHub ArrayList
+ * @return the ContextHubInfo array
+ */
+ /* package */
+ static ContextHubInfo[] createContextHubInfoArray(List<ContextHub> hubList) {
+ ContextHubInfo[] contextHubInfoList = new ContextHubInfo[hubList.size()];
+ for (int i = 0; i < hubList.size(); i++) {
+ contextHubInfoList[i] = new ContextHubInfo(hubList.get(i));
+ }
+
+ return contextHubInfoList;
+ }
+
+ /**
+ * Copies a primitive byte array to a ArrayList<Byte>.
+ *
+ * @param inputArray the primitive byte array
+ * @param outputArray the ArrayList<Byte> array to append
+ */
+ /* package */
+ static void copyToByteArrayList(byte[] inputArray, ArrayList<Byte> outputArray) {
+ outputArray.clear();
+ outputArray.ensureCapacity(inputArray.length);
+ for (byte element : inputArray) {
+ outputArray.add(element);
+ }
+ }
+
+ /**
+ * Creates a byte array given a ArrayList<Byte> and copies its contents.
+ *
+ * @param array the ArrayList<Byte> object
+ * @return the byte array
+ */
+ /* package */
+ static byte[] createPrimitiveByteArray(ArrayList<Byte> array) {
+ byte[] primitiveArray = new byte[array.size()];
+ for (int i = 0; i < array.size(); i++) {
+ primitiveArray[i] = array.get(i);
+ }
+
+ return primitiveArray;
+ }
+
+ /**
+ * Generates the Context Hub HAL's NanoAppBinary object from the client-facing
+ * android.hardware.location.NanoAppBinary object.
+ *
+ * @param nanoAppBinary the client-facing NanoAppBinary object
+ * @return the Context Hub HAL's NanoAppBinary object
+ */
+ /* package */
+ static android.hardware.contexthub.V1_0.NanoAppBinary createHidlNanoAppBinary(
+ NanoAppBinary nanoAppBinary) {
+ android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
+ new android.hardware.contexthub.V1_0.NanoAppBinary();
+
+ hidlNanoAppBinary.appId = nanoAppBinary.getNanoAppId();
+ hidlNanoAppBinary.appVersion = nanoAppBinary.getNanoAppVersion();
+ hidlNanoAppBinary.flags = nanoAppBinary.getFlags();
+ hidlNanoAppBinary.targetChreApiMajorVersion = nanoAppBinary.getTargetChreApiMajorVersion();
+ hidlNanoAppBinary.targetChreApiMinorVersion = nanoAppBinary.getTargetChreApiMinorVersion();
+
+ // Log exceptions while processing the binary, but continue to pass down the binary
+ // since the error checking is deferred to the Context Hub.
+ try {
+ copyToByteArrayList(nanoAppBinary.getBinaryNoHeader(), hidlNanoAppBinary.customBinary);
+ } catch (IndexOutOfBoundsException e) {
+ Log.w(TAG, e.getMessage());
+ } catch (NullPointerException e) {
+ Log.w(TAG, "NanoApp binary was null");
+ }
+
+ return hidlNanoAppBinary;
+ }
+
+ /**
+ * Generates a client-facing NanoAppState array from a HAL HubAppInfo array.
+ *
+ * @param nanoAppInfoList the array of HubAppInfo objects
+ * @return the corresponding array of NanoAppState objects
+ */
+ /* package */
+ static List<NanoAppState> createNanoAppStateList(
+ List<HubAppInfo> nanoAppInfoList) {
+ ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>();
+ for (HubAppInfo appInfo : nanoAppInfoList) {
+ nanoAppStateList.add(
+ new NanoAppState(appInfo.appId, appInfo.version, appInfo.enabled));
+ }
+
+ return nanoAppStateList;
+ }
+
+ /**
+ * Creates a HIDL ContextHubMsg object to send to a nanoapp.
+ *
+ * @param hostEndPoint the ID of the client sending the message
+ * @param message the client-facing NanoAppMessage object describing the message
+ * @return the HIDL ContextHubMsg object
+ */
+ /* package */
+ static ContextHubMsg createHidlContextHubMessage(short hostEndPoint, NanoAppMessage message) {
+ ContextHubMsg hidlMessage = new ContextHubMsg();
+
+ hidlMessage.appName = message.getNanoAppId();
+ hidlMessage.hostEndPoint = hostEndPoint;
+ hidlMessage.msgType = message.getMessageType();
+ copyToByteArrayList(message.getMessageBody(), hidlMessage.msg);
+
+ return hidlMessage;
+ }
+
+ /**
+ * Creates a client-facing NanoAppMessage object to send to a client.
+ *
+ * @param message the HIDL ContextHubMsg object from a nanoapp
+ * @return the NanoAppMessage object
+ */
+ /* package */
+ static NanoAppMessage createNanoAppMessage(ContextHubMsg message) {
+ byte[] messageArray = createPrimitiveByteArray(message.msg);
+
+ return NanoAppMessage.createMessageFromNanoApp(
+ message.appName, message.msgType, messageArray,
+ message.hostEndPoint == HostEndPoint.BROADCAST);
+ }
+
+ /**
+ * Checks for location hardware permissions.
+ *
+ * @param context the context of the service
+ */
+ /* package */
+ static void checkPermissions(Context context) {
+ context.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+ }
+
+ /**
+ * Helper function to convert from the HAL Result enum error code to the
+ * ContextHubTransaction.Result type.
+ *
+ * @param halResult the Result enum error code
+ * @return the ContextHubTransaction.Result equivalent
+ */
+ @ContextHubTransaction.Result
+ /* package */
+ static int toTransactionResult(int halResult) {
+ switch (halResult) {
+ case Result.OK:
+ return ContextHubTransaction.TRANSACTION_SUCCESS;
+ case Result.BAD_PARAMS:
+ return ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS;
+ case Result.NOT_INIT:
+ return ContextHubTransaction.TRANSACTION_FAILED_UNINITIALIZED;
+ case Result.TRANSACTION_PENDING:
+ return ContextHubTransaction.TRANSACTION_FAILED_PENDING;
+ case Result.TRANSACTION_FAILED:
+ case Result.UNKNOWN_FAILURE:
+ default: /* fall through */
+ return ContextHubTransaction.TRANSACTION_FAILED_UNKNOWN;
+ }
+ }
+}
diff --git a/com/android/server/location/ContextHubTransactionManager.java b/com/android/server/location/ContextHubTransactionManager.java
new file mode 100644
index 00000000..47d9d568
--- /dev/null
+++ b/com/android/server/location/ContextHubTransactionManager.java
@@ -0,0 +1,351 @@
+/*
+ * 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.location;
+
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.contexthub.V1_0.TransactionResult;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.IContextHubTransactionCallback;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppState;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Manages transactions at the Context Hub Service.
+ *
+ * This class maintains a queue of transaction requests made to the ContextHubService by clients,
+ * and executes them through the Context Hub. At any point in time, either the transaction queue is
+ * empty, or there is a pending transaction that is waiting for an asynchronous response from the
+ * hub. This class also handles synchronous errors and timeouts of each transaction.
+ *
+ * @hide
+ */
+/* package */ class ContextHubTransactionManager {
+ private static final String TAG = "ContextHubTransactionManager";
+
+ /*
+ * Maximum number of transaction requests that can be pending at a time
+ */
+ private static final int MAX_PENDING_REQUESTS = 10;
+
+ /*
+ * The proxy to talk to the Context Hub
+ */
+ private final IContexthub mContextHubProxy;
+
+ /*
+ * The manager for all clients for the service.
+ */
+ private final ContextHubClientManager mClientManager;
+
+ /*
+ * A queue containing the current transactions
+ */
+ private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
+
+ /*
+ * The next available transaction ID
+ */
+ private final AtomicInteger mNextAvailableId = new AtomicInteger();
+
+ /*
+ * An executor and the future object for scheduling timeout timers
+ */
+ private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1);
+ private ScheduledFuture<?> mTimeoutFuture = null;
+
+ /* package */ ContextHubTransactionManager(
+ IContexthub contextHubProxy, ContextHubClientManager clientManager) {
+ mContextHubProxy = contextHubProxy;
+ mClientManager = clientManager;
+ }
+
+ /**
+ * Creates a transaction for loading a nanoapp.
+ *
+ * @param contextHubId the ID of the hub to load the nanoapp to
+ * @param nanoAppBinary the binary of the nanoapp to load
+ * @param onCompleteCallback the client on complete callback
+ * @return the generated transaction
+ */
+ /* package */ ContextHubServiceTransaction createLoadTransaction(
+ int contextHubId, NanoAppBinary nanoAppBinary,
+ IContextHubTransactionCallback onCompleteCallback) {
+ return new ContextHubServiceTransaction(
+ mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_LOAD_NANOAPP) {
+ @Override
+ /* package */ int onTransact() {
+ android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
+ ContextHubServiceUtil.createHidlNanoAppBinary(nanoAppBinary);
+ try {
+ return mContextHubProxy.loadNanoApp(
+ contextHubId, hidlNanoAppBinary, this.getTransactionId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while trying to load nanoapp with ID 0x" +
+ Long.toHexString(nanoAppBinary.getNanoAppId()));
+ return Result.UNKNOWN_FAILURE;
+ }
+ }
+
+ @Override
+ /* package */ void onTimeout() {
+ onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
+ }
+
+ @Override
+ /* package */ void onTransactionComplete(int result) {
+ try {
+ onCompleteCallback.onTransactionComplete(result);
+ if (result == Result.OK) {
+ mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId());
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling client onTransactionComplete");
+ }
+ }
+ };
+ }
+
+ /**
+ * Creates a transaction for unloading a nanoapp.
+ *
+ * @param contextHubId the ID of the hub to load the nanoapp to
+ * @param nanoAppId the ID of the nanoapp to unload
+ * @param onCompleteCallback the client on complete callback
+ * @return the generated transaction
+ */
+ /* package */ ContextHubServiceTransaction createUnloadTransaction(
+ int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) {
+ return new ContextHubServiceTransaction(
+ mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_UNLOAD_NANOAPP) {
+ @Override
+ /* package */ int onTransact() {
+ try {
+ return mContextHubProxy.unloadNanoApp(
+ contextHubId, nanoAppId, this.getTransactionId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while trying to unload nanoapp with ID 0x" +
+ Long.toHexString(nanoAppId));
+ return Result.UNKNOWN_FAILURE;
+ }
+ }
+
+ @Override
+ /* package */ void onTimeout() {
+ onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
+ }
+
+ @Override
+ /* package */ void onTransactionComplete(int result) {
+ try {
+ onCompleteCallback.onTransactionComplete(result);
+ if (result == Result.OK) {
+ mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling client onTransactionComplete");
+ }
+ }
+ };
+ }
+
+ /**
+ * Creates a transaction for querying for a list of nanoapps.
+ *
+ * @param contextHubId the ID of the hub to query
+ * @param onCompleteCallback the client on complete callback
+ * @return the generated transaction
+ */
+ /* package */ ContextHubServiceTransaction createQueryTransaction(
+ int contextHubId, IContextHubTransactionCallback onCompleteCallback) {
+ return new ContextHubServiceTransaction(
+ mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
+ @Override
+ /* package */ int onTransact() {
+ try {
+ return mContextHubProxy.queryApps(contextHubId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while trying to query for nanoapps");
+ return Result.UNKNOWN_FAILURE;
+ }
+ }
+
+ @Override
+ /* package */ void onTimeout() {
+ onQueryResponse(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT,
+ Collections.emptyList());
+ }
+
+ @Override
+ /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ try {
+ onCompleteCallback.onQueryResponse(result, nanoAppStateList);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling client onQueryComplete");
+ }
+ }
+ };
+ }
+
+ /**
+ * Adds a new transaction to the queue.
+ *
+ * If there was no pending transaction at the time, the transaction that was added will be
+ * started in this method.
+ *
+ * @param transaction the transaction to add
+ * @throws IllegalStateException if the queue is full
+ */
+ /* package */
+ synchronized void addTransaction(
+ ContextHubServiceTransaction transaction) throws IllegalStateException {
+ if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) {
+ throw new IllegalStateException("Transaction transaction queue is full (capacity = "
+ + MAX_PENDING_REQUESTS + ")");
+ }
+ mTransactionQueue.add(transaction);
+
+ if (mTransactionQueue.size() == 1) {
+ startNextTransaction();
+ }
+ }
+
+ /**
+ * Handles a transaction response from a Context Hub.
+ *
+ * @param transactionId the transaction ID of the response
+ * @param result the result of the transaction
+ */
+ /* package */
+ synchronized void onTransactionResponse(int transactionId, int result) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+ return;
+ }
+ if (transaction.getTransactionId() != transactionId) {
+ Log.w(TAG, "Received unexpected transaction response (expected ID = "
+ + transaction.getTransactionId() + ", received ID = " + transactionId + ")");
+ return;
+ }
+
+ transaction.onTransactionComplete(result);
+ removeTransactionAndStartNext();
+ }
+
+ /**
+ * Handles a query response from a Context Hub.
+ *
+ * @param nanoAppStateList the list of nanoapps included in the response
+ */
+ /* package */
+ synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ Log.w(TAG, "Received unexpected query response (no transaction pending)");
+ return;
+ }
+ if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
+ Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
+ return;
+ }
+
+ transaction.onQueryResponse(TransactionResult.SUCCESS, nanoAppStateList);
+ removeTransactionAndStartNext();
+ }
+
+ /**
+ * Handles a hub reset event by stopping a pending transaction and starting the next.
+ */
+ /* package */
+ synchronized void onHubReset() {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ return;
+ }
+
+ removeTransactionAndStartNext();
+ }
+
+ /**
+ * Pops the front transaction from the queue and starts the next pending transaction request.
+ *
+ * Removing elements from the transaction queue must only be done through this method. When a
+ * pending transaction is removed, the timeout timer is cancelled and the transaction is marked
+ * complete.
+ *
+ * It is assumed that the transaction queue is non-empty when this method is invoked, and that
+ * the caller has obtained a lock on this ContextHubTransactionManager object.
+ */
+ private void removeTransactionAndStartNext() {
+ mTimeoutFuture.cancel(false /* mayInterruptIfRunning */);
+
+ ContextHubServiceTransaction transaction = mTransactionQueue.remove();
+ transaction.setComplete();
+
+ if (!mTransactionQueue.isEmpty()) {
+ startNextTransaction();
+ }
+ }
+
+ /**
+ * Starts the next pending transaction request.
+ *
+ * Starting new transactions must only be done through this method. This method continues to
+ * process the transaction queue as long as there are pending requests, and no transaction is
+ * pending.
+ *
+ * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager
+ * object.
+ */
+ private void startNextTransaction() {
+ int result = Result.UNKNOWN_FAILURE;
+ while (result != Result.OK && !mTransactionQueue.isEmpty()) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ result = transaction.onTransact();
+
+ if (result == Result.OK) {
+ Runnable onTimeoutFunc = () -> {
+ synchronized (this) {
+ if (!transaction.isComplete()) {
+ Log.d(TAG, transaction + " timed out");
+ transaction.onTimeout();
+
+ removeTransactionAndStartNext();
+ }
+ }
+ };
+
+ long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS);
+ mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds,
+ TimeUnit.SECONDS);
+ } else {
+ mTransactionQueue.remove();
+ }
+ }
+ }
+}
diff --git a/com/android/server/locksettings/LockSettingsService.java b/com/android/server/locksettings/LockSettingsService.java
index a1a01061..60f451ab 100644
--- a/com/android/server/locksettings/LockSettingsService.java
+++ b/com/android/server/locksettings/LockSettingsService.java
@@ -603,160 +603,156 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private void migrateOldData() {
- try {
- // These Settings moved before multi-user was enabled, so we only have to do it for the
- // root user.
- if (getString("migrated", null, 0) == null) {
- final ContentResolver cr = mContext.getContentResolver();
- for (String validSetting : VALID_SETTINGS) {
- String value = Settings.Secure.getString(cr, validSetting);
- if (value != null) {
- setString(validSetting, value, 0);
- }
+ // These Settings moved before multi-user was enabled, so we only have to do it for the
+ // root user.
+ if (getString("migrated", null, 0) == null) {
+ final ContentResolver cr = mContext.getContentResolver();
+ for (String validSetting : VALID_SETTINGS) {
+ String value = Settings.Secure.getString(cr, validSetting);
+ if (value != null) {
+ setString(validSetting, value, 0);
}
- // No need to move the password / pattern files. They're already in the right place.
- setString("migrated", "true", 0);
- Slog.i(TAG, "Migrated lock settings to new location");
- }
-
- // These Settings changed after multi-user was enabled, hence need to be moved per user.
- if (getString("migrated_user_specific", null, 0) == null) {
- final ContentResolver cr = mContext.getContentResolver();
- List<UserInfo> users = mUserManager.getUsers();
- for (int user = 0; user < users.size(); user++) {
- // Migrate owner info
- final int userId = users.get(user).id;
- final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
- String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
- if (!TextUtils.isEmpty(ownerInfo)) {
- setString(OWNER_INFO, ownerInfo, userId);
- Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId);
- }
+ }
+ // No need to move the password / pattern files. They're already in the right place.
+ setString("migrated", "true", 0);
+ Slog.i(TAG, "Migrated lock settings to new location");
+ }
- // Migrate owner info enabled. Note there was a bug where older platforms only
- // stored this value if the checkbox was toggled at least once. The code detects
- // this case by handling the exception.
- final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
- boolean enabled;
- try {
- int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
- enabled = ivalue != 0;
- setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
- } catch (SettingNotFoundException e) {
- // Setting was never stored. Store it if the string is not empty.
- if (!TextUtils.isEmpty(ownerInfo)) {
- setLong(OWNER_INFO_ENABLED, 1, userId);
- }
- }
- Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
+ // These Settings changed after multi-user was enabled, hence need to be moved per user.
+ if (getString("migrated_user_specific", null, 0) == null) {
+ final ContentResolver cr = mContext.getContentResolver();
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int user = 0; user < users.size(); user++) {
+ // Migrate owner info
+ final int userId = users.get(user).id;
+ final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
+ String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
+ if (!TextUtils.isEmpty(ownerInfo)) {
+ setString(OWNER_INFO, ownerInfo, userId);
+ Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId);
}
- // No need to move the password / pattern files. They're already in the right place.
- setString("migrated_user_specific", "true", 0);
- Slog.i(TAG, "Migrated per-user lock settings to new location");
- }
-
- // Migrates biometric weak such that the fallback mechanism becomes the primary.
- if (getString("migrated_biometric_weak", null, 0) == null) {
- List<UserInfo> users = mUserManager.getUsers();
- for (int i = 0; i < users.size(); i++) {
- int userId = users.get(i).id;
- long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- userId);
- long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- userId);
- if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
- setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- alternateType,
- userId);
+
+ // Migrate owner info enabled. Note there was a bug where older platforms only
+ // stored this value if the checkbox was toggled at least once. The code detects
+ // this case by handling the exception.
+ final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
+ boolean enabled;
+ try {
+ int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
+ enabled = ivalue != 0;
+ setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
+ } catch (SettingNotFoundException e) {
+ // Setting was never stored. Store it if the string is not empty.
+ if (!TextUtils.isEmpty(ownerInfo)) {
+ setLong(OWNER_INFO_ENABLED, 1, userId);
}
- setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ }
+ Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
+ }
+ // No need to move the password / pattern files. They're already in the right place.
+ setString("migrated_user_specific", "true", 0);
+ Slog.i(TAG, "Migrated per-user lock settings to new location");
+ }
+
+ // Migrates biometric weak such that the fallback mechanism becomes the primary.
+ if (getString("migrated_biometric_weak", null, 0) == null) {
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int i = 0; i < users.size(); i++) {
+ int userId = users.get(i).id;
+ long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
+ long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
+ if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
+ setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ alternateType,
userId);
}
- setString("migrated_biometric_weak", "true", 0);
- Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
+ setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
}
+ setString("migrated_biometric_weak", "true", 0);
+ Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
+ }
- // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
- // user was present on the system, so if we're upgrading to M and there is more than one
- // user we disable the flag to remain consistent.
- if (getString("migrated_lockscreen_disabled", null, 0) == null) {
- final List<UserInfo> users = mUserManager.getUsers();
- final int userCount = users.size();
- int switchableUsers = 0;
- for (int i = 0; i < userCount; i++) {
- if (users.get(i).supportsSwitchTo()) {
- switchableUsers++;
- }
+ // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
+ // user was present on the system, so if we're upgrading to M and there is more than one
+ // user we disable the flag to remain consistent.
+ if (getString("migrated_lockscreen_disabled", null, 0) == null) {
+ final List<UserInfo> users = mUserManager.getUsers();
+ final int userCount = users.size();
+ int switchableUsers = 0;
+ for (int i = 0; i < userCount; i++) {
+ if (users.get(i).supportsSwitchTo()) {
+ switchableUsers++;
}
+ }
- if (switchableUsers > 1) {
- for (int i = 0; i < userCount; i++) {
- int id = users.get(i).id;
+ if (switchableUsers > 1) {
+ for (int i = 0; i < userCount; i++) {
+ int id = users.get(i).id;
- if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
- setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
- }
+ if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
+ setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
}
}
-
- setString("migrated_lockscreen_disabled", "true", 0);
- Slog.i(TAG, "Migrated lockscreen disabled flag");
}
- final List<UserInfo> users = mUserManager.getUsers();
- for (int i = 0; i < users.size(); i++) {
- final UserInfo userInfo = users.get(i);
- if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) {
- // When managed profile has a unified lock, the password quality stored has 2
- // possibilities only.
- // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are
- // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC.
- // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for
- // unified lock.
- final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
- if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- // Only possible when it's upgraded from nyc dp3
- Slog.i(TAG, "Migrated tied profile lock type");
- setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id);
- } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
- // It should not happen
- Slog.e(TAG, "Invalid tied profile lock type: " + quality);
- }
+ setString("migrated_lockscreen_disabled", "true", 0);
+ Slog.i(TAG, "Migrated lockscreen disabled flag");
+ }
+
+ final List<UserInfo> users = mUserManager.getUsers();
+ for (int i = 0; i < users.size(); i++) {
+ final UserInfo userInfo = users.get(i);
+ if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) {
+ // When managed profile has a unified lock, the password quality stored has 2
+ // possibilities only.
+ // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are
+ // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC.
+ // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for
+ // unified lock.
+ final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
+ if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ // Only possible when it's upgraded from nyc dp3
+ Slog.i(TAG, "Migrated tied profile lock type");
+ setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id);
+ } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
+ // It should not happen
+ Slog.e(TAG, "Invalid tied profile lock type: " + quality);
}
- try {
- final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id;
- java.security.KeyStore keyStore =
- java.security.KeyStore.getInstance("AndroidKeyStore");
- keyStore.load(null);
- if (keyStore.containsAlias(alias)) {
- keyStore.deleteEntry(alias);
- }
- } catch (KeyStoreException | NoSuchAlgorithmException |
- CertificateException | IOException e) {
- Slog.e(TAG, "Unable to remove tied profile key", e);
+ }
+ try {
+ final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id;
+ java.security.KeyStore keyStore =
+ java.security.KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ if (keyStore.containsAlias(alias)) {
+ keyStore.deleteEntry(alias);
}
+ } catch (KeyStoreException | NoSuchAlgorithmException |
+ CertificateException | IOException e) {
+ Slog.e(TAG, "Unable to remove tied profile key", e);
}
+ }
- boolean isWatch = mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WATCH);
- // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts
- // and device management the lockscreen must be re-enabled now for users that upgrade.
- if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) {
- final int userCount = users.size();
- for (int i = 0; i < userCount; i++) {
- int id = users.get(i).id;
- setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
- }
- setString("migrated_wear_lockscreen_disabled", "true", 0);
- Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
+ boolean isWatch = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WATCH);
+ // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts
+ // and device management the lockscreen must be re-enabled now for users that upgrade.
+ if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) {
+ final int userCount = users.size();
+ for (int i = 0; i < userCount; i++) {
+ int id = users.get(i).id;
+ setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
}
- } catch (RemoteException re) {
- Slog.e(TAG, "Unable to migrate old data", re);
+ setString("migrated_wear_lockscreen_disabled", "true", 0);
+ Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
}
}
@@ -868,7 +864,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
@Override
- public boolean getSeparateProfileChallengeEnabled(int userId) throws RemoteException {
+ public boolean getSeparateProfileChallengeEnabled(int userId) {
checkReadPermission(SEPARATE_PROFILE_CHALLENGE_KEY, userId);
synchronized (mSeparateChallengeLock) {
return getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
@@ -877,7 +873,7 @@ public class LockSettingsService extends ILockSettings.Stub {
@Override
public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
- String managedUserPassword) throws RemoteException {
+ String managedUserPassword) {
checkWritePermission(userId);
synchronized (mSeparateChallengeLock) {
setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
@@ -891,19 +887,19 @@ public class LockSettingsService extends ILockSettings.Stub {
}
@Override
- public void setBoolean(String key, boolean value, int userId) throws RemoteException {
+ public void setBoolean(String key, boolean value, int userId) {
checkWritePermission(userId);
setStringUnchecked(key, userId, value ? "1" : "0");
}
@Override
- public void setLong(String key, long value, int userId) throws RemoteException {
+ public void setLong(String key, long value, int userId) {
checkWritePermission(userId);
setStringUnchecked(key, userId, Long.toString(value));
}
@Override
- public void setString(String key, String value, int userId) throws RemoteException {
+ public void setString(String key, String value, int userId) {
checkWritePermission(userId);
setStringUnchecked(key, userId, value);
}
@@ -2103,7 +2099,7 @@ public class LockSettingsService extends ILockSettings.Stub {
long handle = getSyntheticPasswordHandleLocked(userId);
authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), handle, userCredential, userId);
+ getGateKeeperService(), handle, userCredential, userId, progressCallback);
if (authResult.credentialType != credentialType) {
Slog.e(TAG, "Credential type mismatch.");
@@ -2126,9 +2122,6 @@ public class LockSettingsService extends ILockSettings.Stub {
}
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- if (progressCallback != null) {
- progressCallback.onCredentialVerified();
- }
notifyActivePasswordMetricsAvailable(userCredential, userId);
unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId);
@@ -2227,7 +2220,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
long handle = getSyntheticPasswordHandleLocked(userId);
AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), handle, savedCredential, userId);
+ getGateKeeperService(), handle, savedCredential, userId, null);
VerifyCredentialResponse response = authResult.gkResponse;
AuthenticationToken auth = authResult.authToken;
@@ -2281,7 +2274,7 @@ public class LockSettingsService extends ILockSettings.Stub {
} else /* isSyntheticPasswordBasedCredentialLocked(userId) */ {
long pwdHandle = getSyntheticPasswordHandleLocked(userId);
auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
- pwdHandle, null, userId).authToken;
+ pwdHandle, null, userId, null).authToken;
}
}
if (isSyntheticPasswordBasedCredentialLocked(userId)) {
diff --git a/com/android/server/locksettings/SyntheticPasswordManager.java b/com/android/server/locksettings/SyntheticPasswordManager.java
index 1a1aa569..7a3a746e 100644
--- a/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -781,7 +781,8 @@ public class SyntheticPasswordManager {
* unknown. Caller might choose to validate it by examining AuthenticationResult.credentialType
*/
public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
- long handle, String credential, int userId) throws RemoteException {
+ long handle, String credential, int userId,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
if (credential == null) {
credential = DEFAULT_PASSWORD;
}
@@ -841,7 +842,11 @@ public class SyntheticPasswordManager {
applicationId = transformUnderSecdiscardable(pwdToken,
loadSecdiscardable(handle, userId));
}
-
+ // Supplied credential passes first stage weaver/gatekeeper check so it should be correct.
+ // Notify the callback so the keyguard UI can proceed immediately.
+ if (progressCallback != null) {
+ progressCallback.onCredentialVerified();
+ }
result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
applicationId, sid, userId);
diff --git a/com/android/server/media/AudioPlayerStateMonitor.java b/com/android/server/media/AudioPlayerStateMonitor.java
index be223f1e..7881a952 100644
--- a/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/com/android/server/media/AudioPlayerStateMonitor.java
@@ -16,7 +16,7 @@
package com.android.server.media;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
import android.content.Context;
import android.media.AudioPlaybackConfiguration;
import android.media.IAudioService;
@@ -27,14 +27,14 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.IntArray;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -49,47 +49,57 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor();
/**
- * Called when the state of audio player is changed.
+ * Listener for handling the active state changes of audio players.
*/
- interface OnAudioPlayerStateChangedListener {
- void onAudioPlayerStateChanged(
- int uid, int prevState, @Nullable AudioPlaybackConfiguration config);
+ interface OnAudioPlayerActiveStateChangedListener {
+ /**
+ * Called when the active state of audio player is changed.
+ *
+ * @param config The audio playback configuration for the audio player for which active
+ * state was changed. If {@param isRemoved} is {@code true}, this holds
+ * outdated information.
+ * @param isRemoved {@code true} if the audio player is removed.
+ */
+ void onAudioPlayerActiveStateChanged(
+ @NonNull AudioPlaybackConfiguration config, boolean isRemoved);
}
private final static class MessageHandler extends Handler {
- private static final int MSG_AUDIO_PLAYER_STATE_CHANGED = 1;
+ private static final int MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED = 1;
- private final OnAudioPlayerStateChangedListener mListsner;
+ private final OnAudioPlayerActiveStateChangedListener mListener;
- public MessageHandler(Looper looper, OnAudioPlayerStateChangedListener listener) {
+ MessageHandler(Looper looper, OnAudioPlayerActiveStateChangedListener listener) {
super(looper);
- mListsner = listener;
+ mListener = listener;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_AUDIO_PLAYER_STATE_CHANGED:
- mListsner.onAudioPlayerStateChanged(
- msg.arg1, msg.arg2, (AudioPlaybackConfiguration) msg.obj);
+ case MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED:
+ mListener.onAudioPlayerActiveStateChanged((AudioPlaybackConfiguration) msg.obj,
+ msg.arg1 != 0);
break;
}
}
- public void sendAudioPlayerStateChangedMessage(int uid, int prevState,
- AudioPlaybackConfiguration config) {
- obtainMessage(MSG_AUDIO_PLAYER_STATE_CHANGED, uid, prevState, config).sendToTarget();
+ void sendAudioPlayerActiveStateChangedMessage(
+ final AudioPlaybackConfiguration config, final boolean isRemoved) {
+ obtainMessage(MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED,
+ isRemoved ? 1 : 0, 0 /* unused */, config).sendToTarget();
}
}
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final Map<OnAudioPlayerStateChangedListener, MessageHandler> mListenerMap =
- new HashMap<>();
+ private final Map<OnAudioPlayerActiveStateChangedListener, MessageHandler> mListenerMap =
+ new ArrayMap<>();
@GuardedBy("mLock")
- private final Map<Integer, Integer> mAudioPlayerStates = new HashMap<>();
+ private final Set<Integer> mActiveAudioUids = new ArraySet<>();
@GuardedBy("mLock")
- private final Map<Integer, HashSet<Integer>> mAudioPlayersForUid = new HashMap<>();
+ private ArrayMap<Integer, AudioPlaybackConfiguration> mPrevActiveAudioPlaybackConfigs =
+ new ArrayMap<>();
// Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
// The UID whose audio playback becomes active at the last comes first.
// TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
@@ -122,32 +132,24 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
}
final long token = Binder.clearCallingIdentity();
try {
- final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates);
- final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid =
- new HashMap<>(mAudioPlayersForUid);
synchronized (mLock) {
- mAudioPlayerStates.clear();
- mAudioPlayersForUid.clear();
+ // Update mActiveAudioUids
+ mActiveAudioUids.clear();
+ ArrayMap<Integer, AudioPlaybackConfiguration> activeAudioPlaybackConfigs =
+ new ArrayMap<>();
for (AudioPlaybackConfiguration config : configs) {
- int pii = config.getPlayerInterfaceId();
- int uid = config.getClientUid();
- mAudioPlayerStates.put(pii, config.getPlayerState());
- HashSet<Integer> players = mAudioPlayersForUid.get(uid);
- if (players == null) {
- players = new HashSet<Integer>();
- players.add(pii);
- mAudioPlayersForUid.put(uid, players);
- } else {
- players.add(pii);
+ if (config.isActive()) {
+ mActiveAudioUids.add(config.getClientUid());
+ activeAudioPlaybackConfigs.put(config.getPlayerInterfaceId(), config);
}
}
- for (AudioPlaybackConfiguration config : configs) {
- if (!config.isActive()) {
- continue;
- }
- int uid = config.getClientUid();
- if (!isActiveState(prevAudioPlayerStates.get(config.getPlayerInterfaceId()))) {
+ // Update mSortedAuioPlaybackClientUids.
+ for (int i = 0; i < activeAudioPlaybackConfigs.size(); ++i) {
+ AudioPlaybackConfiguration config = activeAudioPlaybackConfigs.valueAt(i);
+ final int uid = config.getClientUid();
+ if (!mPrevActiveAudioPlaybackConfigs.containsKey(
+ config.getPlayerInterfaceId())) {
if (DEBUG) {
Log.d(TAG, "Found a new active media playback. " +
AudioPlaybackConfiguration.toLogFriendlyString(config));
@@ -163,40 +165,21 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
mSortedAudioPlaybackClientUids.add(0, uid);
}
}
- // Notify the change of audio player states.
+ // Notify the active state change of audio players.
for (AudioPlaybackConfiguration config : configs) {
- final Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId());
- final int prevStateInt =
- (prevState == null) ? AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN :
- prevState.intValue();
- if (prevStateInt != config.getPlayerState()) {
- sendAudioPlayerStateChangedMessageLocked(
- config.getClientUid(), prevStateInt, config);
+ final int pii = config.getPlayerInterfaceId();
+ boolean wasActive = mPrevActiveAudioPlaybackConfigs.remove(pii) != null;
+ if (wasActive != config.isActive()) {
+ sendAudioPlayerActiveStateChangedMessageLocked(
+ config, /* isRemoved */ false);
}
}
- for (Integer prevUid : prevAudioPlayersForUid.keySet()) {
- // If all players for prevUid is removed, notify the prev state was
- // PLAYER_STATE_STARTED only when there were a player whose state was
- // PLAYER_STATE_STARTED, otherwise any inactive state is okay to notify.
- if (!mAudioPlayersForUid.containsKey(prevUid)) {
- Set<Integer> prevPlayers = prevAudioPlayersForUid.get(prevUid);
- int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN;
- for (int pii : prevPlayers) {
- Integer state = prevAudioPlayerStates.get(pii);
- if (state == null) {
- continue;
- }
- if (state == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
- prevState = state;
- break;
- } else if (prevState
- == AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN) {
- prevState = state;
- }
- }
- sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null);
- }
+ for (AudioPlaybackConfiguration config : mPrevActiveAudioPlaybackConfigs.values()) {
+ sendAudioPlayerActiveStateChangedMessageLocked(config, /* isRemoved */ true);
}
+
+ // Update mPrevActiveAudioPlaybackConfigs
+ mPrevActiveAudioPlaybackConfigs = activeAudioPlaybackConfigs;
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -204,9 +187,10 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
}
/**
- * Registers OnAudioPlayerStateChangedListener.
+ * Registers OnAudioPlayerActiveStateChangedListener.
*/
- public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) {
+ public void registerListener(
+ OnAudioPlayerActiveStateChangedListener listener, Handler handler) {
synchronized (mLock) {
mListenerMap.put(listener, new MessageHandler((handler == null) ?
Looper.myLooper() : handler.getLooper(), listener));
@@ -214,9 +198,9 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
}
/**
- * Unregisters OnAudioPlayerStateChangedListener.
+ * Unregisters OnAudioPlayerActiveStateChangedListener.
*/
- public void unregisterListener(OnAudioPlayerStateChangedListener listener) {
+ public void unregisterListener(OnAudioPlayerActiveStateChangedListener listener) {
synchronized (mLock) {
mListenerMap.remove(listener);
}
@@ -239,16 +223,7 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
*/
public boolean isPlaybackActive(int uid) {
synchronized (mLock) {
- Set<Integer> players = mAudioPlayersForUid.get(uid);
- if (players == null) {
- return false;
- }
- for (Integer pii : players) {
- if (isActiveState(mAudioPlayerStates.get(pii))) {
- return true;
- }
- }
- return false;
+ return mActiveAudioUids.contains(uid);
}
}
@@ -314,14 +289,10 @@ class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
}
}
- private void sendAudioPlayerStateChangedMessageLocked(
- final int uid, final int prevState, final AudioPlaybackConfiguration config) {
+ private void sendAudioPlayerActiveStateChangedMessageLocked(
+ final AudioPlaybackConfiguration config, final boolean isRemoved) {
for (MessageHandler messageHandler : mListenerMap.values()) {
- messageHandler.sendAudioPlayerStateChangedMessage(uid, prevState, config);
+ messageHandler.sendAudioPlayerActiveStateChangedMessage(config, isRemoved);
}
}
-
- private static boolean isActiveState(Integer state) {
- return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
- }
}
diff --git a/com/android/server/media/MediaRouterService.java b/com/android/server/media/MediaRouterService.java
index 3c9e1d45..0b089fbc 100644
--- a/com/android/server/media/MediaRouterService.java
+++ b/com/android/server/media/MediaRouterService.java
@@ -19,7 +19,7 @@ package com.android.server.media;
import com.android.internal.util.DumpUtils;
import com.android.server.Watchdog;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -100,7 +100,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub
private final IAudioService mAudioService;
private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
private final Handler mHandler = new Handler();
- private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
+ private final AudioRoutesInfo mAudioRoutesInfo = new AudioRoutesInfo();
+ private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
+ private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
public MediaRouterService(Context context) {
mContext = context;
@@ -111,7 +113,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
mAudioPlayerStateMonitor.registerListener(
- new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
+ new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() {
static final long WAIT_MS = 500;
final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
@Override
@@ -121,39 +123,41 @@ public final class MediaRouterService extends IMediaRouterService.Stub
};
@Override
- public void onAudioPlayerStateChanged(
- int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+ public void onAudioPlayerActiveStateChanged(
+ @NonNull AudioPlaybackConfiguration config, boolean isRemoved) {
+ final boolean active = !isRemoved && config.isActive();
+ final int pii = config.getPlayerInterfaceId();
+ final int uid = config.getClientUid();
+
+ final int idx = mActivePlayerMinPriorityQueue.indexOf(pii);
+ // Keep the latest active player and its uid at the end of the queue.
+ if (idx >= 0) {
+ mActivePlayerMinPriorityQueue.remove(idx);
+ mActivePlayerUidMinPriorityQueue.remove(idx);
+ }
+
int restoreUid = -1;
- boolean active = config == null ? false : config.isActive();
if (active) {
+ mActivePlayerMinPriorityQueue.add(config.getPlayerInterfaceId());
+ mActivePlayerUidMinPriorityQueue.add(uid);
restoreUid = uid;
- } else if (prevState != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
- // Noting to do if the prev state is not an active state.
- return;
- } else {
- IntArray sortedAudioPlaybackClientUids =
- mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
- for (int i = 0; i < sortedAudioPlaybackClientUids.size(); ++i) {
- if (mAudioPlayerStateMonitor.isPlaybackActive(
- sortedAudioPlaybackClientUids.get(i))) {
- restoreUid = sortedAudioPlaybackClientUids.get(i);
- break;
- }
- }
+ } else if (mActivePlayerUidMinPriorityQueue.size() > 0) {
+ restoreUid = mActivePlayerUidMinPriorityQueue.get(
+ mActivePlayerUidMinPriorityQueue.size() - 1);
}
mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
if (restoreUid >= 0) {
restoreRoute(restoreUid);
if (DEBUG) {
- Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
- + " active " + active + " restoring " + restoreUid);
+ Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
+ + ", active=" + active + ", restoreUid=" + restoreUid);
}
} else {
mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
if (DEBUG) {
- Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
- + " active " + active + " delaying");
+ Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
+ + ", active=" + active + ", delaying");
}
}
}
@@ -166,7 +170,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
@Override
public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
synchronized (mLock) {
- if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) {
+ if (newRoutes.mainType != mAudioRoutesInfo.mainType) {
if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
| AudioRoutesInfo.MAIN_HEADPHONES
| AudioRoutesInfo.MAIN_USB)) == 0) {
@@ -176,10 +180,10 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// headset was plugged in.
mGlobalBluetoothA2dpOn = false;
}
- mCurAudioRoutesInfo.mainType = newRoutes.mainType;
+ mAudioRoutesInfo.mainType = newRoutes.mainType;
}
if (!TextUtils.equals(
- newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
+ newRoutes.bluetoothName, mAudioRoutesInfo.bluetoothName)) {
if (newRoutes.bluetoothName == null) {
// BT was disconnected.
mGlobalBluetoothA2dpOn = false;
@@ -187,8 +191,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// BT was connected or changed.
mGlobalBluetoothA2dpOn = true;
}
- mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
+ mAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
}
+ // Although a Bluetooth device is connected before a new audio playback is
+ // started, dispatchAudioRoutChanged() can be called after
+ // onAudioPlayerActiveStateChanged(). That causes restoreBluetoothA2dp()
+ // is called before mGlobalBluetoothA2dpOn is updated.
+ // Calling restoreBluetoothA2dp() here could prevent that.
+ restoreBluetoothA2dp();
}
}
});
@@ -405,12 +415,17 @@ public final class MediaRouterService extends IMediaRouterService.Stub
void restoreBluetoothA2dp() {
try {
+ boolean btConnected = false;
boolean a2dpOn = false;
synchronized (mLock) {
+ btConnected = mAudioRoutesInfo.bluetoothName != null;
a2dpOn = mGlobalBluetoothA2dpOn;
}
- Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
- mAudioService.setBluetoothA2dpOn(a2dpOn);
+ // We don't need to change a2dp status when bluetooth is not connected.
+ if (btConnected) {
+ Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
+ mAudioService.setBluetoothA2dpOn(a2dpOn);
+ }
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
}
diff --git a/com/android/server/media/MediaSessionService.java b/com/android/server/media/MediaSessionService.java
index f6a81d07..06f4f5e8 100644
--- a/com/android/server/media/MediaSessionService.java
+++ b/com/android/server/media/MediaSessionService.java
@@ -16,7 +16,6 @@
package com.android.server.media;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.KeyguardManager;
@@ -138,23 +137,19 @@ public class MediaSessionService extends SystemService implements Monitor {
mAudioService = getAudioService();
mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
mAudioPlayerStateMonitor.registerListener(
- new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
- @Override
- public void onAudioPlayerStateChanged(
- int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
- if (config == null || !config.isActive() || config.getPlayerType()
- == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
- return;
- }
- synchronized (mLock) {
- FullUserRecord user =
- getFullUserRecordLocked(UserHandle.getUserId(uid));
- if (user != null) {
- user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+ (config, isRemoved) -> {
+ if (isRemoved || !config.isActive() || config.getPlayerType()
+ == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ return;
}
- }
- }
- }, null /* handler */);
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(
+ UserHandle.getUserId(config.getClientUid()));
+ if (user != null) {
+ user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+ }
+ }
+ }, null /* handler */);
mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
mContentResolver = getContext().getContentResolver();
mSettingsObserver = new SettingsObserver();
diff --git a/com/android/server/net/NetworkPolicyLogger.java b/com/android/server/net/NetworkPolicyLogger.java
new file mode 100644
index 00000000..2bd9cab3
--- /dev/null
+++ b/com/android/server/net/NetworkPolicyLogger.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.net;
+
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
+
+import android.app.ActivityManager;
+import android.net.NetworkPolicyManager;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.RingBuffer;
+import com.android.server.am.ProcessList;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+public class NetworkPolicyLogger {
+ static final String TAG = "NetworkPolicy";
+
+ static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
+ static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static final int MAX_LOG_SIZE =
+ ActivityManager.isLowRamDeviceStatic() ? 20 : 50;
+ private static final int MAX_NETWORK_BLOCKED_LOG_SIZE =
+ ActivityManager.isLowRamDeviceStatic() ? 50 : 100;
+
+ private static final int EVENT_TYPE_GENERIC = 0;
+ private static final int EVENT_NETWORK_BLOCKED = 1;
+ private static final int EVENT_UID_STATE_CHANGED = 2;
+ private static final int EVENT_POLICIES_CHANGED = 3;
+ private static final int EVENT_METEREDNESS_CHANGED = 4;
+ private static final int EVENT_USER_STATE_REMOVED = 5;
+ private static final int EVENT_RESTRICT_BG_CHANGED = 6;
+ private static final int EVENT_DEVICE_IDLE_MODE_ENABLED = 7;
+ private static final int EVENT_APP_IDLE_STATE_CHANGED = 8;
+ private static final int EVENT_PAROLE_STATE_CHANGED = 9;
+ private static final int EVENT_TEMP_POWER_SAVE_WL_CHANGED = 10;
+ private static final int EVENT_UID_FIREWALL_RULE_CHANGED = 11;
+ private static final int EVENT_FIREWALL_CHAIN_ENABLED = 12;
+
+ static final int NTWK_BLOCKED_POWER = 0;
+ static final int NTWK_ALLOWED_NON_METERED = 1;
+ static final int NTWK_BLOCKED_BLACKLIST = 2;
+ static final int NTWK_ALLOWED_WHITELIST = 3;
+ static final int NTWK_ALLOWED_TMP_WHITELIST = 4;
+ static final int NTWK_BLOCKED_BG_RESTRICT = 5;
+ static final int NTWK_ALLOWED_DEFAULT = 6;
+
+ private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
+ private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
+ private final LogBuffer mEventsBuffer = new LogBuffer(MAX_LOG_SIZE);
+
+ private final Object mLock = new Object();
+
+ void networkBlocked(int uid, int reason) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, uid + " is " + getBlockedReason(reason));
+ mNetworkBlockedBuffer.networkBlocked(uid, reason);
+ }
+ }
+
+ void uidStateChanged(int uid, int procState, long procStateSeq) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG,
+ uid + " state changed to " + procState + " with seq=" + procStateSeq);
+ mUidStateChangeBuffer.uidStateChanged(uid, procState, procStateSeq);
+ }
+ }
+
+ void event(String msg) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG, msg);
+ mEventsBuffer.event(msg);
+ }
+ }
+
+ void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG, getPolicyChangedLog(uid, oldPolicy, newPolicy));
+ mEventsBuffer.uidPolicyChanged(uid, oldPolicy, newPolicy);
+ }
+ }
+
+ void meterednessChanged(int netId, boolean newMetered) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getMeterednessChangedLog(netId, newMetered));
+ mEventsBuffer.meterednessChanged(netId, newMetered);
+ }
+ }
+
+ void removingUserState(int userId) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getUserRemovedLog(userId));
+ mEventsBuffer.userRemoved(userId);
+ }
+ }
+
+ void restrictBackgroundChanged(boolean oldValue, boolean newValue) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG,
+ getRestrictBackgroundChangedLog(oldValue, newValue));
+ mEventsBuffer.restrictBackgroundChanged(oldValue, newValue);
+ }
+ }
+
+ void deviceIdleModeEnabled(boolean enabled) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getDeviceIdleModeEnabled(enabled));
+ mEventsBuffer.deviceIdleModeEnabled(enabled);
+ }
+ }
+
+ void appIdleStateChanged(int uid, boolean idle) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getAppIdleChangedLog(uid, idle));
+ mEventsBuffer.appIdleStateChanged(uid, idle);
+ }
+ }
+
+ void paroleStateChanged(boolean paroleOn) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getParoleStateChanged(paroleOn));
+ mEventsBuffer.paroleStateChanged(paroleOn);
+ }
+ }
+
+ void tempPowerSaveWlChanged(int appId, boolean added) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG, getTempPowerSaveWlChangedLog(appId, added));
+ mEventsBuffer.tempPowerSaveWlChanged(appId, added);
+ }
+ }
+
+ void uidFirewallRuleChanged(int chain, int uid, int rule) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG, getUidFirewallRuleChangedLog(chain, uid, rule));
+ mEventsBuffer.uidFirewallRuleChanged(chain, uid, rule);
+ }
+ }
+
+ void firewallChainEnabled(int chain, boolean enabled) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getFirewallChainEnabledLog(chain, enabled));
+ mEventsBuffer.firewallChainEnabled(chain, enabled);
+ }
+ }
+
+ void firewallRulesChanged(int chain, int[] uids, int[] rules) {
+ synchronized (mLock) {
+ final String log = "Firewall rules changed for " + getFirewallChainName(chain)
+ + "; uids=" + Arrays.toString(uids) + "; rules=" + Arrays.toString(rules);
+ if (LOGD) Slog.d(TAG, log);
+ mEventsBuffer.event(log);
+ }
+ }
+
+ void dumpLogs(IndentingPrintWriter pw) {
+ synchronized (mLock) {
+ pw.println();
+ pw.println("mEventLogs (most recent first):");
+ pw.increaseIndent();
+ mEventsBuffer.reverseDump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("mNetworkBlockedLogs (most recent first):");
+ pw.increaseIndent();
+ mNetworkBlockedBuffer.reverseDump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("mUidStateChangeLogs (most recent first):");
+ pw.increaseIndent();
+ mUidStateChangeBuffer.reverseDump(pw);
+ pw.decreaseIndent();
+ }
+ }
+
+ private static String getBlockedReason(int reason) {
+ switch (reason) {
+ case NTWK_BLOCKED_POWER:
+ return "blocked by power restrictions";
+ case NTWK_ALLOWED_NON_METERED:
+ return "allowed on unmetered network";
+ case NTWK_BLOCKED_BLACKLIST:
+ return "blacklisted on metered network";
+ case NTWK_ALLOWED_WHITELIST:
+ return "whitelisted on metered network";
+ case NTWK_ALLOWED_TMP_WHITELIST:
+ return "temporary whitelisted on metered network";
+ case NTWK_BLOCKED_BG_RESTRICT:
+ return "blocked when background is restricted";
+ case NTWK_ALLOWED_DEFAULT:
+ return "allowed by default";
+ default:
+ return String.valueOf(reason);
+ }
+ }
+
+ private static String getPolicyChangedLog(int uid, int oldPolicy, int newPolicy) {
+ return "Policy for " + uid + " changed from "
+ + NetworkPolicyManager.uidPoliciesToString(oldPolicy) + " to "
+ + NetworkPolicyManager.uidPoliciesToString(newPolicy);
+ }
+
+ private static String getMeterednessChangedLog(int netId, boolean newMetered) {
+ return "Meteredness of netId=" + netId + " changed to " + newMetered;
+ }
+
+ private static String getUserRemovedLog(int userId) {
+ return "Remove state for u" + userId;
+ }
+
+ private static String getRestrictBackgroundChangedLog(boolean oldValue, boolean newValue) {
+ return "Changed restrictBackground: " + oldValue + "->" + newValue;
+ }
+
+ private static String getDeviceIdleModeEnabled(boolean enabled) {
+ return "DeviceIdleMode enabled: " + enabled;
+ }
+
+ private static String getAppIdleChangedLog(int uid, boolean idle) {
+ return "App idle state of uid " + uid + ": " + idle;
+ }
+
+ private static String getParoleStateChanged(boolean paroleOn) {
+ return "Parole state: " + paroleOn;
+ }
+
+ private static String getTempPowerSaveWlChangedLog(int appId, boolean added) {
+ return "temp-power-save whitelist for " + appId + " changed to: " + added;
+ }
+
+ private static String getUidFirewallRuleChangedLog(int chain, int uid, int rule) {
+ return String.format("Firewall rule changed: %d-%s-%s",
+ uid, getFirewallChainName(chain), getFirewallRuleName(rule));
+ }
+
+ private static String getFirewallChainEnabledLog(int chain, boolean enabled) {
+ return "Firewall chain " + getFirewallChainName(chain) + " state: " + enabled;
+ }
+
+ private static String getFirewallChainName(int chain) {
+ switch (chain) {
+ case FIREWALL_CHAIN_DOZABLE:
+ return FIREWALL_CHAIN_NAME_DOZABLE;
+ case FIREWALL_CHAIN_STANDBY:
+ return FIREWALL_CHAIN_NAME_STANDBY;
+ case FIREWALL_CHAIN_POWERSAVE:
+ return FIREWALL_CHAIN_NAME_POWERSAVE;
+ default:
+ return String.valueOf(chain);
+ }
+ }
+
+ private static String getFirewallRuleName(int rule) {
+ switch (rule) {
+ case FIREWALL_RULE_DEFAULT:
+ return "default";
+ case FIREWALL_RULE_ALLOW:
+ return "allow";
+ case FIREWALL_RULE_DENY:
+ return "deny";
+ default:
+ return String.valueOf(rule);
+ }
+ }
+
+ private final static class LogBuffer extends RingBuffer<Data> {
+ private static final SimpleDateFormat sFormatter
+ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSS");
+ private static final Date sDate = new Date();
+
+ public LogBuffer(int capacity) {
+ super(Data.class, capacity);
+ }
+
+ public void uidStateChanged(int uid, int procState, long procStateSeq) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_UID_STATE_CHANGED;
+ data.ifield1 = uid;
+ data.ifield2 = procState;
+ data.lfield1 = procStateSeq;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void event(String msg) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_TYPE_GENERIC;
+ data.sfield1 = msg;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void networkBlocked(int uid, int reason) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_NETWORK_BLOCKED;
+ data.ifield1 = uid;
+ data.ifield2 = reason;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_POLICIES_CHANGED;
+ data.ifield1 = uid;
+ data.ifield2 = oldPolicy;
+ data.ifield3 = newPolicy;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void meterednessChanged(int netId, boolean newMetered) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_METEREDNESS_CHANGED;
+ data.ifield1 = netId;
+ data.bfield1 = newMetered;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void userRemoved(int userId) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_USER_STATE_REMOVED;
+ data.ifield1 = userId;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void restrictBackgroundChanged(boolean oldValue, boolean newValue) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_RESTRICT_BG_CHANGED;
+ data.bfield1 = oldValue;
+ data.bfield2 = newValue;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void deviceIdleModeEnabled(boolean enabled) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_DEVICE_IDLE_MODE_ENABLED;
+ data.bfield1 = enabled;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void appIdleStateChanged(int uid, boolean idle) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_APP_IDLE_STATE_CHANGED;
+ data.ifield1 = uid;
+ data.bfield1 = idle;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void paroleStateChanged(boolean paroleOn) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_PAROLE_STATE_CHANGED;
+ data.bfield1 = paroleOn;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void tempPowerSaveWlChanged(int appId, boolean added) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_TEMP_POWER_SAVE_WL_CHANGED;
+ data.ifield1 = appId;
+ data.bfield1 = added;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void uidFirewallRuleChanged(int chain, int uid, int rule) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_UID_FIREWALL_RULE_CHANGED;
+ data.ifield1 = chain;
+ data.ifield2 = uid;
+ data.ifield3 = rule;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void firewallChainEnabled(int chain, boolean enabled) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_FIREWALL_CHAIN_ENABLED;
+ data.ifield1 = chain;
+ data.bfield1 = enabled;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void reverseDump(IndentingPrintWriter pw) {
+ final Data[] allData = toArray();
+ for (int i = allData.length - 1; i >= 0; --i) {
+ if (allData[i] == null) {
+ pw.println("NULL");
+ continue;
+ }
+ pw.print(formatDate(allData[i].timeStamp));
+ pw.print(" - ");
+ pw.println(getContent(allData[i]));
+ }
+ }
+
+ public String getContent(Data data) {
+ switch (data.type) {
+ case EVENT_TYPE_GENERIC:
+ return data.sfield1;
+ case EVENT_NETWORK_BLOCKED:
+ return data.ifield1 + "-" + getBlockedReason(data.ifield2);
+ case EVENT_UID_STATE_CHANGED:
+ return data.ifield1 + "-" + ProcessList.makeProcStateString(data.ifield2)
+ + "-" + data.lfield1;
+ case EVENT_POLICIES_CHANGED:
+ return getPolicyChangedLog(data.ifield1, data.ifield2, data.ifield3);
+ case EVENT_METEREDNESS_CHANGED:
+ return getMeterednessChangedLog(data.ifield1, data.bfield1);
+ case EVENT_USER_STATE_REMOVED:
+ return getUserRemovedLog(data.ifield1);
+ case EVENT_RESTRICT_BG_CHANGED:
+ return getRestrictBackgroundChangedLog(data.bfield1, data.bfield2);
+ case EVENT_DEVICE_IDLE_MODE_ENABLED:
+ return getDeviceIdleModeEnabled(data.bfield1);
+ case EVENT_APP_IDLE_STATE_CHANGED:
+ return getAppIdleChangedLog(data.ifield1, data.bfield1);
+ case EVENT_PAROLE_STATE_CHANGED:
+ return getParoleStateChanged(data.bfield1);
+ case EVENT_TEMP_POWER_SAVE_WL_CHANGED:
+ return getTempPowerSaveWlChangedLog(data.ifield1, data.bfield1);
+ case EVENT_UID_FIREWALL_RULE_CHANGED:
+ return getUidFirewallRuleChangedLog(data.ifield1, data.ifield2, data.ifield3);
+ case EVENT_FIREWALL_CHAIN_ENABLED:
+ return getFirewallChainEnabledLog(data.ifield1, data.bfield1);
+ default:
+ return String.valueOf(data.type);
+ }
+ }
+
+ private String formatDate(long millis) {
+ sDate.setTime(millis);
+ return sFormatter.format(sDate);
+ }
+ }
+
+ public final static class Data {
+ int type;
+ long timeStamp;
+
+ int ifield1;
+ int ifield2;
+ int ifield3;
+ long lfield1;
+ boolean bfield1;
+ boolean bfield2;
+ String sfield1;
+
+ public void reset(){
+ sfield1 = null;
+ }
+ }
+}
diff --git a/com/android/server/net/NetworkPolicyManagerService.java b/com/android/server/net/NetworkPolicyManagerService.java
index 3fa3cd49..fdfe2418 100644
--- a/com/android/server/net/NetworkPolicyManagerService.java
+++ b/com/android/server/net/NetworkPolicyManagerService.java
@@ -82,6 +82,13 @@ import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BLACKLIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -248,9 +255,9 @@ import java.util.concurrent.TimeUnit;
* </ul>
*/
public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
- static final String TAG = "NetworkPolicy";
- private static final boolean LOGD = false;
- private static final boolean LOGV = false;
+ static final String TAG = NetworkPolicyLogger.TAG;
+ private static final boolean LOGD = NetworkPolicyLogger.LOGD;
+ private static final boolean LOGV = NetworkPolicyLogger.LOGV;
private static final int VERSION_INIT = 1;
private static final int VERSION_ADDED_SNOOZE = 2;
@@ -265,13 +272,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final int VERSION_ADDED_CYCLE = 11;
private static final int VERSION_LATEST = VERSION_ADDED_CYCLE;
- /**
- * Max items written to {@link #ProcStateSeqHistory}.
- */
- @VisibleForTesting
- public static final int MAX_PROC_STATE_SEQ_HISTORY =
- ActivityManager.isLowRamDeviceStatic() ? 50 : 200;
-
@VisibleForTesting
public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING;
@VisibleForTesting
@@ -471,13 +471,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private ActivityManagerInternal mActivityManagerInternal;
- /**
- * This is used for debugging purposes. Whenever the IUidObserver.onUidStateChanged is called,
- * the uid and procStateSeq will be written to this and will be printed as part of dump.
- */
- @VisibleForTesting
- public ProcStateSeqHistory mObservedHistory
- = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
+ private final NetworkPolicyLogger mLogger = new NetworkPolicyLogger();
// TODO: keep whitelist of system-critical services that should never have
// rules enforced, such as system, phone, and radio UIDs.
@@ -966,6 +960,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
if ((oldMetered != newMetered) || mNetworkMetered.indexOfKey(network.netId) < 0) {
+ mLogger.meterednessChanged(network.netId, newMetered);
mNetworkMetered.put(network.netId, newMetered);
updateNetworkRulesNL();
}
@@ -2148,6 +2143,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE);
if (oldPolicy != policy) {
setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+ mLogger.uidPolicyChanged(uid, oldPolicy, policy);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -2168,6 +2164,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
policy |= oldPolicy;
if (oldPolicy != policy) {
setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+ mLogger.uidPolicyChanged(uid, oldPolicy, policy);
}
}
}
@@ -2185,6 +2182,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
policy = oldPolicy & ~policy;
if (oldPolicy != policy) {
setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+ mLogger.uidPolicyChanged(uid, oldPolicy, policy);
}
}
}
@@ -2264,7 +2262,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
*/
boolean removeUserStateUL(int userId, boolean writePolicy) {
- if (LOGV) Slog.v(TAG, "removeUserStateUL()");
+ mLogger.removingUserState(userId);
boolean changed = false;
// Remove entries from revoked default restricted background UID whitelist
@@ -2429,7 +2427,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@Override
public void onTetheringChanged(String iface, boolean tethering) {
// No need to enforce permission because setRestrictBackground() will do it.
- if (LOGD) Log.d(TAG, "onTetherStateChanged(" + iface + ", " + tethering + ")");
synchronized (mUidRulesFirstLock) {
if (mRestrictBackground && tethering) {
Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver");
@@ -2486,6 +2483,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
sendRestrictBackgroundChangedMsg();
+ mLogger.restrictBackgroundChanged(oldRestrictBackground, mRestrictBackground);
if (mRestrictBackgroundPowerState.globalBatterySaverEnabled) {
mRestrictBackgroundChangedInBsm = true;
@@ -2551,6 +2549,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
return;
}
mDeviceIdleMode = enabled;
+ mLogger.deviceIdleModeEnabled(enabled);
if (mSystemReady) {
// Device idle change means we need to rebuild rules for all
// known apps, so do a global refresh.
@@ -2964,10 +2963,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
fout.decreaseIndent();
- fout.println("Observed uid state changes:");
- fout.increaseIndent();
- mObservedHistory.dumpUL(fout);
- fout.decreaseIndent();
+ mLogger.dumpLogs(fout);
}
}
}
@@ -3750,8 +3746,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
try {
final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
- if (LOGV) Log.v(TAG, "onAppIdleStateChanged(): uid=" + uid + ", idle=" + idle);
synchronized (mUidRulesFirstLock) {
+ mLogger.appIdleStateChanged(uid, idle);
updateRuleForAppIdleUL(uid);
updateRulesForPowerRestrictionsUL(uid);
}
@@ -3762,6 +3758,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@Override
public void onParoleStateChanged(boolean isParoleOn) {
synchronized (mUidRulesFirstLock) {
+ mLogger.paroleStateChanged(isParoleOn);
updateRulesForAppIdleParoleUL();
}
}
@@ -3947,7 +3944,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
synchronized (mUidRulesFirstLock) {
// We received a uid state change callback, add it to the history so that it
// will be useful for debugging.
- mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
+ mLogger.uidStateChanged(uid, procState, procStateSeq);
// Now update the network policy rules as per the updated uid state.
updateUidStateUL(uid, procState);
// Updating the network rules is done, so notify AMS about this.
@@ -4081,6 +4078,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
rules[index] = uidRules.valueAt(index);
}
mNetworkManager.setFirewallUidRules(chain, uids, rules);
+ mLogger.firewallRulesChanged(chain, uids, rules);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem setting firewall uid rules", e);
} catch (RemoteException e) {
@@ -4107,6 +4105,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
try {
mNetworkManager.setFirewallUidRule(chain, uid, rule);
+ mLogger.uidFirewallRuleChanged(chain, uid, rule);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem setting firewall uid rules", e);
} catch (RemoteException e) {
@@ -4129,6 +4128,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
mFirewallChainStates.put(chain, enable);
try {
mNetworkManager.setFirewallChainEnabled(chain, enable);
+ mLogger.firewallChainEnabled(chain, enable);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem enable firewall chain", e);
} catch (RemoteException e) {
@@ -4305,30 +4305,30 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
isBackgroundRestricted = mRestrictBackground;
}
if (hasRule(uidRules, RULE_REJECT_ALL)) {
- if (LOGV) logUidStatus(uid, "blocked by power restrictions");
+ mLogger.networkBlocked(uid, NTWK_BLOCKED_POWER);
return true;
}
if (!isNetworkMetered) {
- if (LOGV) logUidStatus(uid, "allowed on unmetered network");
+ mLogger.networkBlocked(uid, NTWK_ALLOWED_NON_METERED);
return false;
}
if (hasRule(uidRules, RULE_REJECT_METERED)) {
- if (LOGV) logUidStatus(uid, "blacklisted on metered network");
+ mLogger.networkBlocked(uid, NTWK_BLOCKED_BLACKLIST);
return true;
}
if (hasRule(uidRules, RULE_ALLOW_METERED)) {
- if (LOGV) logUidStatus(uid, "whitelisted on metered network");
+ mLogger.networkBlocked(uid, NTWK_ALLOWED_WHITELIST);
return false;
}
if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
- if (LOGV) logUidStatus(uid, "temporary whitelisted on metered network");
+ mLogger.networkBlocked(uid, NTWK_ALLOWED_TMP_WHITELIST);
return false;
}
if (isBackgroundRestricted) {
- if (LOGV) logUidStatus(uid, "blocked when background is restricted");
+ mLogger.networkBlocked(uid, NTWK_BLOCKED_BG_RESTRICT);
return true;
}
- if (LOGV) logUidStatus(uid, "allowed by default");
+ mLogger.networkBlocked(uid, NTWK_ALLOWED_DEFAULT);
return false;
}
@@ -4379,6 +4379,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@Override
public void onTempPowerSaveWhitelistChange(int appId, boolean added) {
synchronized (mUidRulesFirstLock) {
+ mLogger.tempPowerSaveWlChanged(appId, added);
if (added) {
mPowerSaveTempWhitelistAppIds.put(appId, true);
} else {
@@ -4393,80 +4394,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
return (uidRules & rule) != 0;
}
- private static void logUidStatus(int uid, String descr) {
- Slog.d(TAG, String.format("uid %d is %s", uid, descr));
- }
-
- /**
- * This class is used for storing and dumping the last {@link #MAX_PROC_STATE_SEQ_HISTORY}
- * (uid, procStateSeq) pairs.
- */
- @VisibleForTesting
- public static final class ProcStateSeqHistory {
- private static final int INVALID_UID = -1;
-
- /**
- * Denotes maximum number of items this history can hold.
- */
- private final int mMaxCapacity;
- /**
- * Used for storing the uid information.
- */
- private final int[] mUids;
- /**
- * Used for storing the sequence numbers associated with {@link #mUids}.
- */
- private final long[] mProcStateSeqs;
- /**
- * Points to the next available slot for writing (uid, procStateSeq) pair.
- */
- private int mHistoryNext;
-
- public ProcStateSeqHistory(int maxCapacity) {
- mMaxCapacity = maxCapacity;
- mUids = new int[mMaxCapacity];
- Arrays.fill(mUids, INVALID_UID);
- mProcStateSeqs = new long[mMaxCapacity];
- }
-
- @GuardedBy("mUidRulesFirstLock")
- public void addProcStateSeqUL(int uid, long procStateSeq) {
- mUids[mHistoryNext] = uid;
- mProcStateSeqs[mHistoryNext] = procStateSeq;
- mHistoryNext = increaseNext(mHistoryNext, 1);
- }
-
- @GuardedBy("mUidRulesFirstLock")
- public void dumpUL(IndentingPrintWriter fout) {
- if (mUids[0] == INVALID_UID) {
- fout.println("NONE");
- return;
- }
- int index = mHistoryNext;
- do {
- index = increaseNext(index, -1);
- if (mUids[index] == INVALID_UID) {
- break;
- }
- fout.println(getString(mUids[index], mProcStateSeqs[index]));
- } while (index != mHistoryNext);
- }
-
- public static String getString(int uid, long procStateSeq) {
- return "UID=" + uid + " Seq=" + procStateSeq;
- }
-
- private int increaseNext(int next, int increment) {
- next += increment;
- if (next >= mMaxCapacity) {
- next = 0;
- } else if (next < 0) {
- next = mMaxCapacity - 1;
- }
- return next;
- }
- }
-
private class NotificationId {
private final String mTag;
private final int mId;
diff --git a/com/android/server/notification/NotificationManagerService.java b/com/android/server/notification/NotificationManagerService.java
index 557ba427..bec6fc2c 100644
--- a/com/android/server/notification/NotificationManagerService.java
+++ b/com/android/server/notification/NotificationManagerService.java
@@ -16,15 +16,21 @@
package com.android.server.notification;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.USER_NULL;
import static android.service.notification.NotificationListenerService
+ .HINT_HOST_DISABLE_CALL_EFFECTS;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
+import static android.service.notification.NotificationListenerService
+ .HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
+import static android.service.notification.NotificationListenerService
.NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
import static android.service.notification.NotificationListenerService
.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
@@ -32,12 +38,13 @@ import static android.service.notification.NotificationListenerService
.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static android.service.notification.NotificationListenerService.REASON_ERROR;
-import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService
+ .REASON_GROUP_SUMMARY_CANCELED;
import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
@@ -48,14 +55,10 @@ import static android.service.notification.NotificationListenerService.REASON_SN
import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.service.notification.NotificationListenerService.TRIM_FULL;
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
-
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -68,17 +71,17 @@ import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
-import android.app.NotificationChannelGroup;
-import android.app.backup.BackupManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationManager.Policy;
+import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.app.backup.BackupManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.companion.ICompanionDeviceManager;
@@ -119,8 +122,8 @@ import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.Vibrator;
import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
@@ -149,7 +152,6 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
-import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
@@ -174,9 +176,10 @@ import com.android.server.SystemService;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
+import com.android.server.notification.ManagedServices.UserProfiles;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.notification.ManagedServices.UserProfiles;
+import com.android.server.wm.WindowManagerInternal;
import libcore.io.IoUtils;
@@ -196,7 +199,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
-import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -736,6 +738,11 @@ public class NotificationManagerService extends SystemService {
for (NotificationVisibility nv : newlyVisibleKeys) {
NotificationRecord r = mNotificationsByKey.get(nv.key);
if (r == null) continue;
+ if (!r.isSeen()) {
+ // Report to usage stats that notification was made visible
+ if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key);
+ reportSeen(r);
+ }
r.setVisibility(true, nv.rank);
nv.recycle();
}
@@ -766,7 +773,7 @@ public class NotificationManagerService extends SystemService {
.setType(expanded ? MetricsEvent.TYPE_DETAIL
: MetricsEvent.TYPE_COLLAPSE));
}
- if (expanded) {
+ if (expanded && userAction) {
r.recordExpanded();
}
EventLogTags.writeNotificationExpansion(key,
@@ -1515,7 +1522,11 @@ public class NotificationManagerService extends SystemService {
}
}
}
+ final NotificationChannel preUpdate =
+ mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+
mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
+ maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
if (!fromListener) {
final NotificationChannel modifiedChannel =
@@ -1528,12 +1539,40 @@ public class NotificationManagerService extends SystemService {
savePolicyFile();
}
+ private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel preUpdate,
+ NotificationChannel update) {
+ try {
+ if ((preUpdate.getImportance() == IMPORTANCE_NONE
+ && update.getImportance() != IMPORTANCE_NONE)
+ || (preUpdate.getImportance() != IMPORTANCE_NONE
+ && update.getImportance() == IMPORTANCE_NONE)) {
+ getContext().sendBroadcastAsUser(
+ new Intent(ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED)
+ .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID,
+ update.getId())
+ .putExtra(NotificationManager.EXTRA_BLOCKED_STATE,
+ update.getImportance() == IMPORTANCE_NONE)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setPackage(pkg),
+ UserHandle.of(UserHandle.getUserId(uid)), null);
+ }
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't notify app about channel change", e);
+ }
+ }
+
private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
boolean fromApp, boolean fromListener) {
Preconditions.checkNotNull(group);
Preconditions.checkNotNull(pkg);
+
+ final NotificationChannelGroup preUpdate =
+ mRankingHelper.getNotificationChannelGroup(group.getId(), pkg, uid);
mRankingHelper.createNotificationChannelGroup(pkg, uid, group,
fromApp);
+ if (!fromApp) {
+ maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group);
+ }
if (!fromListener) {
mListeners.notifyNotificationChannelGroupChanged(pkg,
UserHandle.of(UserHandle.getCallingUserId()), group,
@@ -1541,6 +1580,25 @@ public class NotificationManagerService extends SystemService {
}
}
+ private void maybeNotifyChannelGroupOwner(String pkg, int uid,
+ NotificationChannelGroup preUpdate, NotificationChannelGroup update) {
+ try {
+ if (preUpdate.isBlocked() != update.isBlocked()) {
+ getContext().sendBroadcastAsUser(
+ new Intent(ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED)
+ .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID,
+ update.getId())
+ .putExtra(NotificationManager.EXTRA_BLOCKED_STATE,
+ update.isBlocked())
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setPackage(pkg),
+ UserHandle.of(UserHandle.getUserId(uid)), null);
+ }
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't notify app about group change", e);
+ }
+ }
+
private ArrayList<ComponentName> getSuppressors() {
ArrayList<ComponentName> names = new ArrayList<ComponentName>();
for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) {
@@ -1643,6 +1701,14 @@ public class NotificationManagerService extends SystemService {
return INotificationManager.Stub.asInterface(mService);
}
+ protected void reportSeen(NotificationRecord r) {
+ final int userId = r.sbn.getUserId();
+ mAppUsageStats.reportEvent(r.sbn.getPackageName(),
+ userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM
+ : userId,
+ UsageEvents.Event.NOTIFICATION_SEEN);
+ }
+
@VisibleForTesting
NotificationManagerInternal getInternalService() {
return mInternalService;
@@ -1911,11 +1977,18 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
+ checkCallerIsSystemOrSameApp(pkg);
+ return mRankingHelper.getNotificationChannelGroupWithChannels(
+ pkg, Binder.getCallingUid(), groupId, false);
+ }
+
+ @Override
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(
String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return new ParceledListSlice<>(new ArrayList(
- mRankingHelper.getNotificationChannelGroups(pkg, Binder.getCallingUid())));
+ return mRankingHelper.getNotificationChannelGroups(
+ pkg, Binder.getCallingUid(), false, false);
}
@Override
@@ -1985,7 +2058,7 @@ public class NotificationManagerService extends SystemService {
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
String pkg, int uid, boolean includeDeleted) {
checkCallerIsSystem();
- return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted);
+ return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true);
}
@Override
@@ -2269,10 +2342,7 @@ public class NotificationManagerService extends SystemService {
}
if (!r.isSeen()) {
if (DBG) Slog.d(TAG, "Marking notification as seen " + keys[i]);
- mAppUsageStats.reportEvent(r.sbn.getPackageName(),
- userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM
- : userId,
- UsageEvents.Event.USER_INTERACTION);
+ reportSeen(r);
r.setSeen();
}
}
@@ -4673,7 +4743,8 @@ public class NotificationManagerService extends SystemService {
}
void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
- if (!mAccessibilityManager.isEnabled()) {
+ if (!mAccessibilityManager.isObservedEventType(
+ AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)) {
return;
}
diff --git a/com/android/server/notification/RankingConfig.java b/com/android/server/notification/RankingConfig.java
index b9c0d907..b1b0bf26 100644
--- a/com/android/server/notification/RankingConfig.java
+++ b/com/android/server/notification/RankingConfig.java
@@ -36,7 +36,7 @@ public interface RankingConfig {
void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
boolean fromTargetApp);
ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid, boolean includeDeleted);
+ int uid, boolean includeDeleted, boolean includeNonGrouped);
void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp);
void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
diff --git a/com/android/server/notification/RankingHelper.java b/com/android/server/notification/RankingHelper.java
index d566a450..c0dccb53 100644
--- a/com/android/server/notification/RankingHelper.java
+++ b/com/android/server/notification/RankingHelper.java
@@ -750,12 +750,15 @@ public class RankingHelper implements RankingConfig {
int uid) {
Preconditions.checkNotNull(pkg);
Record r = getRecord(pkg, uid);
+ if (r == null) {
+ return null;
+ }
return r.groups.get(groupId);
}
@Override
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid, boolean includeDeleted) {
+ int uid, boolean includeDeleted, boolean includeNonGrouped) {
Preconditions.checkNotNull(pkg);
Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
Record r = getRecord(pkg, uid);
@@ -783,7 +786,7 @@ public class RankingHelper implements RankingConfig {
}
}
}
- if (nonGrouped.getChannels().size() > 0) {
+ if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
groups.put(null, nonGrouped);
}
return new ParceledListSlice<>(new ArrayList<>(groups.values()));
diff --git a/com/android/server/pm/BackgroundDexOptService.java b/com/android/server/pm/BackgroundDexOptService.java
index 679250cb..8591304e 100644
--- a/com/android/server/pm/BackgroundDexOptService.java
+++ b/com/android/server/pm/BackgroundDexOptService.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
+import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -40,6 +41,7 @@ import com.android.server.PinnerService;
import com.android.server.pm.dex.DexoptOptions;
import java.io.File;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.TimeUnit;
@@ -402,14 +404,22 @@ public class BackgroundDexOptService extends JobService {
}
/**
- * Execute the idle optimizations immediately.
+ * Execute idle optimizations immediately on packages in packageNames. If packageNames is null,
+ * then execute on all packages.
*/
- public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context) {
+ public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context,
+ @Nullable List<String> packageNames) {
// Create a new object to make sure we don't interfere with the scheduled jobs.
// Note that this may still run at the same time with the job scheduled by the
// JobScheduler but the scheduler will not be able to cancel it.
BackgroundDexOptService bdos = new BackgroundDexOptService();
- int result = bdos.idleOptimization(pm, pm.getOptimizablePackages(), context);
+ ArraySet<String> packagesToOptimize;
+ if (packageNames == null) {
+ packagesToOptimize = pm.getOptimizablePackages();
+ } else {
+ packagesToOptimize = new ArraySet<>(packageNames);
+ }
+ int result = bdos.idleOptimization(pm, packagesToOptimize, context);
return result == OPTIMIZE_PROCESSED;
}
diff --git a/com/android/server/pm/Installer.java b/com/android/server/pm/Installer.java
index 210eb138..6a06be2f 100644
--- a/com/android/server/pm/Installer.java
+++ b/com/android/server/pm/Installer.java
@@ -486,6 +486,16 @@ public class Installer extends SystemService {
}
}
+ public byte[] hashSecondaryDexFile(String dexPath, String packageName, int uid,
+ @Nullable String volumeUuid, int flags) throws InstallerException {
+ if (!checkBeforeRemote()) return new byte[0];
+ try {
+ return mInstalld.hashSecondaryDexFile(dexPath, packageName, uid, volumeUuid, flags);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public void invalidateMounts() throws InstallerException {
if (!checkBeforeRemote()) return;
try {
diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java
index 29f48ee3..00cfa310 100644
--- a/com/android/server/pm/PackageDexOptimizer.java
+++ b/com/android/server/pm/PackageDexOptimizer.java
@@ -154,7 +154,13 @@ public class PackageDexOptimizer {
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
final List<String> paths = pkg.getAllCodePaths();
- final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+
+ int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+ if (sharedGid == -1) {
+ Slog.wtf(TAG, "Well this is awkward; package " + pkg.applicationInfo.name + " had UID "
+ + pkg.applicationInfo.uid, new Throwable());
+ sharedGid = android.os.Process.NOBODY_UID;
+ }
// Get the class loader context dependencies.
// For each code path in the package, this array contains the class loader context that
diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java
index 83cffe57..2d5f7c71 100644
--- a/com/android/server/pm/PackageManagerService.java
+++ b/com/android/server/pm/PackageManagerService.java
@@ -81,15 +81,12 @@ import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING;
import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.content.pm.PackageParser.PARSE_IS_OEM;
-import static android.content.pm.PackageParser.PARSE_IS_PRIVILEGED;
import static android.content.pm.PackageParser.isApkFile;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
-
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
@@ -169,11 +166,13 @@ import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
+import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ActivityIntentInfo;
import android.content.pm.PackageParser.Package;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.ParseFlags;
import android.content.pm.PackageStats;
import android.content.pm.PackageUserState;
import android.content.pm.ParceledListSlice;
@@ -293,6 +292,7 @@ import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.pm.Settings.VersionInfo;
+import com.android.server.pm.dex.DexLogger;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.PackageDexUsage;
@@ -447,27 +447,48 @@ public class PackageManagerService extends IPackageManager.Stub
// package apks to install directory.
private static final String INSTALL_PACKAGE_SUFFIX = "-";
- static final int SCAN_NO_DEX = 1<<1;
- static final int SCAN_FORCE_DEX = 1<<2;
- static final int SCAN_UPDATE_SIGNATURE = 1<<3;
- static final int SCAN_NEW_INSTALL = 1<<4;
- static final int SCAN_UPDATE_TIME = 1<<5;
- static final int SCAN_BOOTING = 1<<6;
- static final int SCAN_TRUSTED_OVERLAY = 1<<7;
- static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<8;
- static final int SCAN_REPLACING = 1<<9;
- static final int SCAN_REQUIRE_KNOWN = 1<<10;
- static final int SCAN_MOVE = 1<<11;
- static final int SCAN_INITIAL = 1<<12;
- static final int SCAN_CHECK_ONLY = 1<<13;
- static final int SCAN_DONT_KILL_APP = 1<<14;
- static final int SCAN_IGNORE_FROZEN = 1<<15;
- static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<16;
- static final int SCAN_AS_INSTANT_APP = 1<<17;
- static final int SCAN_AS_FULL_APP = 1<<18;
- static final int SCAN_AS_VIRTUAL_PRELOAD = 1<<19;
- /** Should not be with the scan flags */
- static final int FLAGS_REMOVE_CHATTY = 1<<31;
+ static final int SCAN_NO_DEX = 1<<0;
+ static final int SCAN_UPDATE_SIGNATURE = 1<<1;
+ static final int SCAN_NEW_INSTALL = 1<<2;
+ static final int SCAN_UPDATE_TIME = 1<<3;
+ static final int SCAN_BOOTING = 1<<4;
+ static final int SCAN_TRUSTED_OVERLAY = 1<<5;
+ static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<6;
+ static final int SCAN_REQUIRE_KNOWN = 1<<7;
+ static final int SCAN_MOVE = 1<<8;
+ static final int SCAN_INITIAL = 1<<9;
+ static final int SCAN_CHECK_ONLY = 1<<10;
+ static final int SCAN_DONT_KILL_APP = 1<<11;
+ static final int SCAN_IGNORE_FROZEN = 1<<12;
+ static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<13;
+ static final int SCAN_AS_INSTANT_APP = 1<<14;
+ static final int SCAN_AS_FULL_APP = 1<<15;
+ static final int SCAN_AS_VIRTUAL_PRELOAD = 1<<16;
+ static final int SCAN_AS_SYSTEM = 1<<17;
+ static final int SCAN_AS_PRIVILEGED = 1<<18;
+ static final int SCAN_AS_OEM = 1<<19;
+
+ @IntDef(flag = true, prefix = { "SCAN_" }, value = {
+ SCAN_NO_DEX,
+ SCAN_UPDATE_SIGNATURE,
+ SCAN_NEW_INSTALL,
+ SCAN_UPDATE_TIME,
+ SCAN_BOOTING,
+ SCAN_TRUSTED_OVERLAY,
+ SCAN_DELETE_DATA_ON_FAILURES,
+ SCAN_REQUIRE_KNOWN,
+ SCAN_MOVE,
+ SCAN_INITIAL,
+ SCAN_CHECK_ONLY,
+ SCAN_DONT_KILL_APP,
+ SCAN_IGNORE_FROZEN,
+ SCAN_FIRST_BOOT_OR_UPGRADE,
+ SCAN_AS_INSTANT_APP,
+ SCAN_AS_FULL_APP,
+ SCAN_AS_VIRTUAL_PRELOAD,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScanFlags {}
private static final String STATIC_SHARED_LIB_DELIMITER = "_";
/** Extension of the compressed packages */
@@ -2380,7 +2401,10 @@ public class PackageManagerService extends IPackageManager.Stub
mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
"*dexopt*");
- mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
+ DexManager.Listener dexManagerListener = DexLogger.getListener(this,
+ installer, mInstallLock);
+ mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock,
+ dexManagerListener);
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -2511,32 +2535,44 @@ public class PackageManagerService extends IPackageManager.Stub
// Collect vendor overlay packages. (Do this before scanning any apps.)
// For security and version matching reason, only consider
// overlay packages if they reside in the right directory.
- scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
+ scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),
+ mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM_DIR,
+ scanFlags
+ | SCAN_AS_SYSTEM
+ | SCAN_TRUSTED_OVERLAY,
+ 0);
mParallelPackageParserCallback.findStaticOverlayPackages();
// Find base frameworks (resource packages without code).
- scanDirTracedLI(frameworkDir, mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_IS_PRIVILEGED,
- scanFlags | SCAN_NO_DEX, 0);
+ scanDirTracedLI(frameworkDir,
+ mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM_DIR,
+ scanFlags
+ | SCAN_NO_DEX
+ | SCAN_AS_SYSTEM
+ | SCAN_AS_PRIVILEGED,
+ 0);
// Collected privileged system packages.
final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
- scanDirTracedLI(privilegedAppDir, mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);
+ scanDirTracedLI(privilegedAppDir,
+ mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM_DIR,
+ scanFlags
+ | SCAN_AS_SYSTEM
+ | SCAN_AS_PRIVILEGED,
+ 0);
// Collect ordinary system packages.
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
- scanDirTracedLI(systemAppDir, mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
+ scanDirTracedLI(systemAppDir,
+ mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM_DIR,
+ scanFlags
+ | SCAN_AS_SYSTEM,
+ 0);
// Collect all vendor packages.
File vendorAppDir = new File("/vendor/app");
@@ -2545,16 +2581,22 @@ public class PackageManagerService extends IPackageManager.Stub
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
- scanDirTracedLI(vendorAppDir, mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
+ scanDirTracedLI(vendorAppDir,
+ mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM_DIR,
+ scanFlags
+ | SCAN_AS_SYSTEM,
+ 0);
// Collect all OEM packages.
final File oemAppDir = new File(Environment.getOemDirectory(), "app");
- scanDirTracedLI(oemAppDir, mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_IS_OEM, scanFlags, 0);
+ scanDirTracedLI(oemAppDir,
+ mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM_DIR,
+ scanFlags
+ | SCAN_AS_SYSTEM
+ | SCAN_AS_OEM,
+ 0);
// Prune any system packages that no longer exist.
final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
@@ -2711,21 +2753,38 @@ public class PackageManagerService extends IPackageManager.Stub
logCriticalInfo(Log.WARN, "Expected better " + packageName
+ " but never showed up; reverting to system");
- int reparseFlags = mDefParseFlags;
+ final @ParseFlags int reparseFlags;
+ final @ScanFlags int rescanFlags;
if (FileUtils.contains(privilegedAppDir, scanFile)) {
- reparseFlags = PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_IS_PRIVILEGED;
+ reparseFlags =
+ mDefParseFlags |
+ PackageParser.PARSE_IS_SYSTEM_DIR;
+ rescanFlags =
+ scanFlags
+ | SCAN_AS_SYSTEM
+ | SCAN_AS_PRIVILEGED;
} else if (FileUtils.contains(systemAppDir, scanFile)) {
- reparseFlags = PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR;
+ reparseFlags =
+ mDefParseFlags |
+ PackageParser.PARSE_IS_SYSTEM_DIR;
+ rescanFlags =
+ scanFlags
+ | SCAN_AS_SYSTEM;
} else if (FileUtils.contains(vendorAppDir, scanFile)) {
- reparseFlags = PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR;
+ reparseFlags =
+ mDefParseFlags |
+ PackageParser.PARSE_IS_SYSTEM_DIR;
+ rescanFlags =
+ scanFlags
+ | SCAN_AS_SYSTEM;
} else if (FileUtils.contains(oemAppDir, scanFile)) {
- reparseFlags = PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_IS_OEM;
+ reparseFlags =
+ mDefParseFlags |
+ PackageParser.PARSE_IS_SYSTEM_DIR;
+ rescanFlags =
+ scanFlags
+ | SCAN_AS_SYSTEM
+ | SCAN_AS_OEM;
} else {
Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
continue;
@@ -2734,7 +2793,7 @@ public class PackageManagerService extends IPackageManager.Stub
mSettings.enableSystemPackageLPw(packageName);
try {
- scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null);
+ scanPackageTracedLI(scanFile, reparseFlags, rescanFlags, 0, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to parse original system package: "
+ e.getMessage());
@@ -3581,7 +3640,7 @@ public class PackageManagerService extends IPackageManager.Stub
final int N = list.size();
for (int i = 0; i < N; i++) {
ResolveInfo info = list.get(i);
- if (packageName.equals(info.activityInfo.packageName)) {
+ if (info.priority >= 0 && packageName.equals(info.activityInfo.packageName)) {
return true;
}
}
@@ -7986,96 +8045,95 @@ public class PackageManagerService extends IPackageManager.Stub
return finalList;
}
- private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + dir.getAbsolutePath() + "]");
+ private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
- scanDirLI(dir, parseFlags, scanFlags, currentTime);
+ scanDirLI(scanDir, parseFlags, scanFlags, currentTime);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
- private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
- final File[] files = dir.listFiles();
+ private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
+ final File[] files = scanDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
- Log.d(TAG, "No files in app dir " + dir);
+ Log.d(TAG, "No files in app dir " + scanDir);
return;
}
if (DEBUG_PACKAGE_SCANNING) {
- Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
+ Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags
+ " flags=0x" + Integer.toHexString(parseFlags));
}
- ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
+ try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
- mParallelPackageParserCallback);
-
- // Submit files for parsing in parallel
- int fileCount = 0;
- for (File file : files) {
- final boolean isPackage = (isApkFile(file) || file.isDirectory())
- && !PackageInstallerService.isStageName(file.getName());
- if (!isPackage) {
- // Ignore entries which are not packages
- continue;
+ mParallelPackageParserCallback)) {
+ // Submit files for parsing in parallel
+ int fileCount = 0;
+ for (File file : files) {
+ final boolean isPackage = (isApkFile(file) || file.isDirectory())
+ && !PackageInstallerService.isStageName(file.getName());
+ if (!isPackage) {
+ // Ignore entries which are not packages
+ continue;
+ }
+ parallelPackageParser.submit(file, parseFlags);
+ fileCount++;
}
- parallelPackageParser.submit(file, parseFlags);
- fileCount++;
- }
- // Process results one by one
- for (; fileCount > 0; fileCount--) {
- ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
- Throwable throwable = parseResult.throwable;
- int errorCode = PackageManager.INSTALL_SUCCEEDED;
+ // Process results one by one
+ for (; fileCount > 0; fileCount--) {
+ ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
+ Throwable throwable = parseResult.throwable;
+ int errorCode = PackageManager.INSTALL_SUCCEEDED;
- if (throwable == null) {
- // Static shared libraries have synthetic package names
- if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
- renameStaticSharedLibraryPackage(parseResult.pkg);
- }
- try {
- if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
- scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags,
- currentTime, null);
+ if (throwable == null) {
+ // Static shared libraries have synthetic package names
+ if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
+ renameStaticSharedLibraryPackage(parseResult.pkg);
}
- } catch (PackageManagerException e) {
+ try {
+ if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
+ scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
+ currentTime, null);
+ }
+ } catch (PackageManagerException e) {
+ errorCode = e.error;
+ Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
+ }
+ } else if (throwable instanceof PackageParser.PackageParserException) {
+ PackageParser.PackageParserException e = (PackageParser.PackageParserException)
+ throwable;
errorCode = e.error;
- Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
+ Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
+ } else {
+ throw new IllegalStateException("Unexpected exception occurred while parsing "
+ + parseResult.scanFile, throwable);
}
- } else if (throwable instanceof PackageParser.PackageParserException) {
- PackageParser.PackageParserException e = (PackageParser.PackageParserException)
- throwable;
- errorCode = e.error;
- Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
- } else {
- throw new IllegalStateException("Unexpected exception occurred while parsing "
- + parseResult.scanFile, throwable);
- }
- // Delete invalid userdata apps
- if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
- errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) {
- logCriticalInfo(Log.WARN,
- "Deleting invalid package at " + parseResult.scanFile);
- removeCodePathLI(parseResult.scanFile);
+ // Delete invalid userdata apps
+ if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&
+ errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) {
+ logCriticalInfo(Log.WARN,
+ "Deleting invalid package at " + parseResult.scanFile);
+ removeCodePathLI(parseResult.scanFile);
+ }
}
}
- parallelPackageParser.close();
}
public static void reportSettingsProblem(int priority, String msg) {
logCriticalInfo(priority, msg);
}
- private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile,
- final int policyFlags) throws PackageManagerException {
+ private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg,
+ final @ParseFlags int parseFlags) throws PackageManagerException {
// When upgrading from pre-N MR1, verify the package time stamp using the package
// directory and not the APK file.
final long lastModifiedTime = mIsPreNMR1Upgrade
- ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg, srcFile);
+ ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg);
if (ps != null
- && ps.codePath.equals(srcFile)
+ && ps.codePathString.equals(pkg.codePath)
&& ps.timeStamp == lastModifiedTime
&& !isCompatSignatureUpdateNeeded(pkg)
&& !isRecoverSignatureUpdateNeeded(pkg)) {
@@ -8098,12 +8156,12 @@ public class PackageManagerService extends IPackageManager.Stub
Slog.w(TAG, "PackageSetting for " + ps.name
+ " is missing signatures. Collecting certs again to recover them.");
} else {
- Slog.i(TAG, srcFile.toString() + " changed; collecting certs");
+ Slog.i(TAG, toString() + " changed; collecting certs");
}
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
- PackageParser.collectCertificates(pkg, policyFlags);
+ PackageParser.collectCertificates(pkg, parseFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
} finally {
@@ -8138,10 +8196,6 @@ public class PackageManagerService extends IPackageManager.Stub
pp.setDisplayMetrics(mMetrics);
pp.setCallback(mPackageParserCallback);
- if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
- parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
- }
-
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final PackageParser.Package pkg;
try {
@@ -8157,16 +8211,17 @@ public class PackageManagerService extends IPackageManager.Stub
renameStaticSharedLibraryPackage(pkg);
}
- return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);
+ return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user);
}
/**
* Scans a package and returns the newly parsed package.
* @throws PackageManagerException on a parse error.
*/
- private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,
- final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
- throws PackageManagerException {
+ private PackageParser.Package scanPackageChildLI(PackageParser.Package pkg,
+ final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
+ @Nullable UserHandle user)
+ throws PackageManagerException {
// If the package has children and this is the first dive in the function
// we scan the package with the SCAN_CHECK_ONLY flag set to see whether all
// packages (parent and children) would be successfully scanned before the
@@ -8181,20 +8236,20 @@ public class PackageManagerService extends IPackageManager.Stub
}
// Scan the parent
- PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, scanFile, policyFlags,
+ PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, parseFlags,
scanFlags, currentTime, user);
// Scan the children
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPackage = pkg.childPackages.get(i);
- scanPackageInternalLI(childPackage, scanFile, policyFlags, scanFlags,
+ scanPackageInternalLI(childPackage, parseFlags, scanFlags,
currentTime, user);
}
if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
- return scanPackageLI(pkg, scanFile, policyFlags, scanFlags, currentTime, user);
+ return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user);
}
return scannedPkg;
@@ -8204,11 +8259,12 @@ public class PackageManagerService extends IPackageManager.Stub
* Scans a package and returns the newly parsed package.
* @throws PackageManagerException on a parse error.
*/
- private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, File scanFile,
- int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
- throws PackageManagerException {
+ private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg,
+ @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
+ @Nullable UserHandle user)
+ throws PackageManagerException {
PackageSetting ps = null;
- PackageSetting updatedPkg;
+ PackageSetting updatedPs;
// reader
synchronized (mPackages) {
// Look to see if we already know about this package.
@@ -8225,14 +8281,14 @@ public class PackageManagerService extends IPackageManager.Stub
// Check to see if this package could be hiding/updating a system
// package. Must look for it either under the original or real
// package name depending on our state.
- updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
- if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
+ updatedPs = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
+ if (DEBUG_INSTALL && updatedPs != null) Slog.d(TAG, "updatedPkg = " + updatedPs);
// If this is a package we don't know about on the system partition, we
// may need to remove disabled child packages on the system partition
// or may need to not add child packages if the parent apk is updated
// on the data partition and no longer defines this child package.
- if ((policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0) {
+ if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
// If this is a parent package for an updated system app and this system
// app got an OTA update which no longer defines some of the child packages
// we have to prune them from the disabled system packages.
@@ -8260,28 +8316,27 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- final boolean isUpdatedPkg = updatedPkg != null;
- final boolean isUpdatedSystemPkg = isUpdatedPkg
- && (policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0;
+ final boolean isUpdatedPkg = updatedPs != null;
+ final boolean isUpdatedSystemPkg = isUpdatedPkg && (scanFlags & SCAN_AS_SYSTEM) != 0;
boolean isUpdatedPkgBetter = false;
// First check if this is a system package that may involve an update
if (isUpdatedSystemPkg) {
// If new package is not located in "/system/priv-app" (e.g. due to an OTA),
// it needs to drop FLAG_PRIVILEGED.
- if (locationIsPrivileged(scanFile)) {
- updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+ if (locationIsPrivileged(pkg.codePath)) {
+ updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
} else {
- updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+ updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
// If new package is not located in "/oem" (e.g. due to an OTA),
// it needs to drop FLAG_OEM.
- if (locationIsOem(scanFile)) {
- updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
+ if (locationIsOem(pkg.codePath)) {
+ updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
} else {
- updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM;
+ updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM;
}
- if (ps != null && !ps.codePath.equals(scanFile)) {
+ if (ps != null && !ps.codePathString.equals(pkg.codePath)) {
// The path has changed from what was last scanned... check the
// version of the new path against what we have stored to determine
// what to do.
@@ -8289,26 +8344,27 @@ public class PackageManagerService extends IPackageManager.Stub
if (pkg.mVersionCode <= ps.versionCode) {
// The system package has been updated and the code path does not match
// Ignore entry. Skip it.
- if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + scanFile
+ if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + pkg.codePath
+ " ignored: updated version " + ps.versionCode
+ " better than this " + pkg.mVersionCode);
- if (!updatedPkg.codePath.equals(scanFile)) {
+ if (!updatedPs.codePathString.equals(pkg.codePath)) {
Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg "
- + ps.name + " changing from " + updatedPkg.codePathString
- + " to " + scanFile);
- updatedPkg.codePath = scanFile;
- updatedPkg.codePathString = scanFile.toString();
- updatedPkg.resourcePath = scanFile;
- updatedPkg.resourcePathString = scanFile.toString();
+ + ps.name + " changing from " + updatedPs.codePathString
+ + " to " + pkg.codePath);
+ final File codePath = new File(pkg.codePath);
+ updatedPs.codePath = codePath;
+ updatedPs.codePathString = pkg.codePath;
+ updatedPs.resourcePath = codePath;
+ updatedPs.resourcePathString = pkg.codePath;
}
- updatedPkg.pkg = pkg;
- updatedPkg.versionCode = pkg.mVersionCode;
+ updatedPs.pkg = pkg;
+ updatedPs.versionCode = pkg.mVersionCode;
// Update the disabled system child packages to point to the package too.
- final int childCount = updatedPkg.childPackageNames != null
- ? updatedPkg.childPackageNames.size() : 0;
+ final int childCount = updatedPs.childPackageNames != null
+ ? updatedPs.childPackageNames.size() : 0;
for (int i = 0; i < childCount; i++) {
- String childPackageName = updatedPkg.childPackageNames.get(i);
+ String childPackageName = updatedPs.childPackageNames.get(i);
PackageSetting updatedChildPkg = mSettings.getDisabledSystemPkgLPr(
childPackageName);
if (updatedChildPkg != null) {
@@ -8329,7 +8385,7 @@ public class PackageManagerService extends IPackageManager.Stub
mPackages.remove(ps.name);
}
- logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+ logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath
+ " reverting from " + ps.codePathString
+ ": new version " + pkg.mVersionCode
+ " better than installed " + ps.versionCode);
@@ -8349,7 +8405,7 @@ public class PackageManagerService extends IPackageManager.Stub
String resourcePath = null;
String baseResourcePath = null;
- if ((policyFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !isUpdatedPkgBetter) {
+ if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !isUpdatedPkgBetter) {
if (ps != null && ps.resourcePathString != null) {
resourcePath = ps.resourcePathString;
baseResourcePath = ps.resourcePathString;
@@ -8376,39 +8432,38 @@ public class PackageManagerService extends IPackageManager.Stub
if (isUpdatedSystemPkg && !isUpdatedPkgBetter) {
// Set CPU Abis to application info.
if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
- final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPkg);
- derivePackageAbi(pkg, scanFile, cpuAbiOverride, false, mAppLib32InstallDir);
+ final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPs);
+ derivePackageAbi(pkg, cpuAbiOverride, false, mAppLib32InstallDir);
} else {
- pkg.applicationInfo.primaryCpuAbi = updatedPkg.primaryCpuAbiString;
- pkg.applicationInfo.secondaryCpuAbi = updatedPkg.secondaryCpuAbiString;
+ pkg.applicationInfo.primaryCpuAbi = updatedPs.primaryCpuAbiString;
+ pkg.applicationInfo.secondaryCpuAbi = updatedPs.secondaryCpuAbiString;
}
- pkg.mExtras = updatedPkg;
+ pkg.mExtras = updatedPs;
throw new PackageManagerException(Log.WARN, "Package " + ps.name + " at "
- + scanFile + " ignored: updated version " + ps.versionCode
+ + pkg.codePath + " ignored: updated version " + ps.versionCode
+ " better than this " + pkg.mVersionCode);
}
if (isUpdatedPkg) {
- // An updated system app will not have the PARSE_IS_SYSTEM flag set
- // initially
- policyFlags |= PackageParser.PARSE_IS_SYSTEM;
+ // updated system applications don't initially have the SCAN_AS_SYSTEM flag set
+ scanFlags |= SCAN_AS_SYSTEM;
- // An updated privileged app will not have the PARSE_IS_PRIVILEGED
+ // An updated privileged application will not have the PARSE_IS_PRIVILEGED
// flag set initially
- if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
- policyFlags |= PackageParser.PARSE_IS_PRIVILEGED;
+ if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+ scanFlags |= SCAN_AS_PRIVILEGED;
}
// An updated OEM app will not have the PARSE_IS_OEM
// flag set initially
- if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
- policyFlags |= PackageParser.PARSE_IS_OEM;
+ if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
+ scanFlags |= SCAN_AS_OEM;
}
}
// Verify certificates against what was last scanned
- collectCertificatesLI(ps, pkg, scanFile, policyFlags);
+ collectCertificatesLI(ps, pkg, parseFlags);
/*
* A new system app appeared, but we already had a non-system one of the
@@ -8416,7 +8471,7 @@ public class PackageManagerService extends IPackageManager.Stub
*/
boolean shouldHideSystemApp = false;
if (!isUpdatedPkg && ps != null
- && (policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
+ && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
/*
* Check to make sure the signatures match first. If they don't,
* wipe the installed application and its data.
@@ -8438,7 +8493,7 @@ public class PackageManagerService extends IPackageManager.Stub
*/
if (pkg.mVersionCode <= ps.versionCode) {
shouldHideSystemApp = true;
- logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile
+ logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + pkg.codePath
+ " but new version " + pkg.mVersionCode + " better than installed "
+ ps.versionCode + "; hiding system");
} else {
@@ -8448,7 +8503,7 @@ public class PackageManagerService extends IPackageManager.Stub
* already-installed application and replace it with our own
* while keeping the application data.
*/
- logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+ logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath
+ " reverting from " + ps.codePathString + ": new version "
+ pkg.mVersionCode + " better than installed " + ps.versionCode);
InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
@@ -8464,9 +8519,9 @@ public class PackageManagerService extends IPackageManager.Stub
// are kept in different files. (except for app in either system or
// vendor path).
// TODO grab this value from PackageSettings
- if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
- policyFlags |= PackageParser.PARSE_FORWARD_LOCK;
+ parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
}
}
@@ -8479,7 +8534,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
// Note that we invoke the following method only if we are about to unpack an application
- PackageParser.Package scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags
+ PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
| SCAN_UPDATE_SIGNATURE, currentTime, user);
/*
@@ -8985,11 +9040,11 @@ public class PackageManagerService extends IPackageManager.Stub
* Execute the background dexopt job immediately.
*/
@Override
- public boolean runBackgroundDexoptJob() {
+ public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) {
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
return false;
}
- return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext);
+ return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext, packageNames);
}
List<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
@@ -9481,8 +9536,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg,
- final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
- throws PackageManagerException {
+ final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
+ @Nullable UserHandle user) throws PackageManagerException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
// If the package has children and this is the first dive in the function
// we recursively scan the package with the SCAN_CHECK_ONLY flag set to see
@@ -9500,12 +9555,12 @@ public class PackageManagerService extends IPackageManager.Stub
final PackageParser.Package scannedPkg;
try {
// Scan the parent
- scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags, currentTime, user);
+ scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags, currentTime, user);
// Scan the children
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPkg = pkg.childPackages.get(i);
- scanPackageLI(childPkg, policyFlags,
+ scanPackageLI(childPkg, parseFlags,
scanFlags, currentTime, user);
}
} finally {
@@ -9513,18 +9568,18 @@ public class PackageManagerService extends IPackageManager.Stub
}
if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
- return scanPackageTracedLI(pkg, policyFlags, scanFlags, currentTime, user);
+ return scanPackageTracedLI(pkg, parseFlags, scanFlags, currentTime, user);
}
return scannedPkg;
}
- private PackageParser.Package scanPackageLI(PackageParser.Package pkg, final int policyFlags,
- int scanFlags, long currentTime, @Nullable UserHandle user)
- throws PackageManagerException {
+ private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
+ final @ParseFlags int parseFlags, final @ScanFlags int scanFlags, long currentTime,
+ @Nullable UserHandle user) throws PackageManagerException {
boolean success = false;
try {
- final PackageParser.Package res = scanPackageDirtyLI(pkg, policyFlags, scanFlags,
+ final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,
currentTime, user);
success = true;
return res;
@@ -9587,16 +9642,17 @@ public class PackageManagerService extends IPackageManager.Stub
}
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
- final int policyFlags, final int scanFlags, long currentTime, @Nullable UserHandle user)
+ final @ParseFlags int parseFlags, final @ScanFlags int scanFlags, long currentTime,
+ @Nullable UserHandle user)
throws PackageManagerException {
if (DEBUG_PACKAGE_SCANNING) {
- if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)
+ if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
Log.d(TAG, "Scanning package " + pkg.packageName);
}
- applyPolicy(pkg, policyFlags);
+ applyPolicy(pkg, parseFlags, scanFlags);
- assertPackageIsValid(pkg, policyFlags, scanFlags);
+ assertPackageIsValid(pkg, parseFlags, scanFlags);
if (Build.IS_DEBUGGABLE &&
pkg.isPrivileged() &&
@@ -9629,7 +9685,7 @@ public class PackageManagerService extends IPackageManager.Stub
suid = mSettings.getSharedUserLPw(
pkg.mSharedUserId, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
if (DEBUG_PACKAGE_SCANNING) {
- if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)
+ if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" + suid.userId
+ "): packages=" + suid.packages);
}
@@ -9797,7 +9853,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
if ((scanFlags & SCAN_BOOTING) == 0
- && (policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
// Check all shared libraries and map to their actual file path.
// We only do this here for apps not on a system dir, because those
// are the only ones that can fail an install due to this. We
@@ -9834,7 +9890,7 @@ public class PackageManagerService extends IPackageManager.Stub
// over the latest parsed certs.
pkgSetting.signatures.mSignatures = pkg.mSignatures;
} else {
- if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Package " + pkg.packageName + " upgrade keys do not match the "
+ "previously installed version");
@@ -9861,7 +9917,7 @@ public class PackageManagerService extends IPackageManager.Stub
// over the latest parsed certs.
pkgSetting.signatures.mSignatures = pkg.mSignatures;
} catch (PackageManagerException e) {
- if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
throw e;
}
// The signature has changed, but this package is in the system
@@ -9921,8 +9977,7 @@ public class PackageManagerService extends IPackageManager.Stub
if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
final boolean extractNativeLibs = !pkg.isLibrary();
- derivePackageAbi(pkg, scanFile, cpuAbiOverride, extractNativeLibs,
- mAppLib32InstallDir);
+ derivePackageAbi(pkg, cpuAbiOverride, extractNativeLibs, mAppLib32InstallDir);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
// Some system apps still use directory structure for native libraries
@@ -10029,7 +10084,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
// Take care of first install / last update times.
- final long scanFileTime = getLastModifiedTime(pkg, scanFile);
+ final long scanFileTime = getLastModifiedTime(pkg);
if (currentTime != 0) {
if (pkgSetting.firstInstallTime == 0) {
pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime;
@@ -10039,7 +10094,7 @@ public class PackageManagerService extends IPackageManager.Stub
} else if (pkgSetting.firstInstallTime == 0) {
// We need *something*. Take time time stamp of the file.
pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime;
- } else if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
+ } else if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
if (scanFileTime != pkgSetting.timeStamp) {
// A package on the system image has changed; consider this
// to be an update.
@@ -10058,7 +10113,7 @@ public class PackageManagerService extends IPackageManager.Stub
final int userId = user == null ? 0 : user.getIdentifier();
// Modify state for the given package setting
commitPackageSettings(pkg, pkgSetting, user, scanFlags,
- (policyFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
+ (parseFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
if (pkgSetting.getInstantApp(userId)) {
mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId);
}
@@ -10073,8 +10128,9 @@ public class PackageManagerService extends IPackageManager.Stub
* Implementation detail: This method must NOT have any side effect. It would
* ideally be static, but, it requires locks to read system state.
*/
- private void applyPolicy(PackageParser.Package pkg, int policyFlags) {
- if ((policyFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
+ private void applyPolicy(PackageParser.Package pkg, final @ParseFlags int parseFlags,
+ final @ScanFlags int scanFlags) {
+ if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
if (pkg.applicationInfo.isDirectBootAware()) {
// we're direct boot aware; set for all components
@@ -10095,21 +10151,60 @@ public class PackageManagerService extends IPackageManager.Stub
pkg.isStub = true;
}
} else {
- // Only allow system apps to be flagged as core apps.
+ // non system apps can't be flagged as core
pkg.coreApp = false;
// clear flags not applicable to regular apps
+ pkg.applicationInfo.flags &=
+ ~ApplicationInfo.FLAG_PERSISTENT;
pkg.applicationInfo.privateFlags &=
~ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
pkg.applicationInfo.privateFlags &=
~ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
+ // clear protected broadcasts
+ pkg.protectedBroadcasts = null;
+ // cap permission priorities
+ if (pkg.permissionGroups != null && pkg.permissionGroups.size() > 0) {
+ for (int i = pkg.permissionGroups.size() - 1; i >= 0; --i) {
+ pkg.permissionGroups.get(i).info.priority = 0;
+ }
+ }
+ }
+ if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
+ // ignore export request for single user receivers
+ if (pkg.receivers != null) {
+ for (int i = pkg.receivers.size() - 1; i >= 0; --i) {
+ final PackageParser.Activity receiver = pkg.receivers.get(i);
+ if ((receiver.info.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+ receiver.info.exported = false;
+ }
+ }
+ }
+ // ignore export request for single user services
+ if (pkg.services != null) {
+ for (int i = pkg.services.size() - 1; i >= 0; --i) {
+ final PackageParser.Service service = pkg.services.get(i);
+ if ((service.info.flags & ServiceInfo.FLAG_SINGLE_USER) != 0) {
+ service.info.exported = false;
+ }
+ }
+ }
+ // ignore export request for single user providers
+ if (pkg.providers != null) {
+ for (int i = pkg.providers.size() - 1; i >= 0; --i) {
+ final PackageParser.Provider provider = pkg.providers.get(i);
+ if ((provider.info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0) {
+ provider.info.exported = false;
+ }
+ }
+ }
}
- pkg.mTrustedOverlay = (policyFlags&PackageParser.PARSE_TRUSTED_OVERLAY) != 0;
+ pkg.mTrustedOverlay = (scanFlags & SCAN_TRUSTED_OVERLAY) != 0;
- if ((policyFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) {
+ if ((scanFlags & SCAN_AS_PRIVILEGED) != 0) {
pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
- if ((policyFlags&PackageParser.PARSE_IS_OEM) != 0) {
+ if ((scanFlags & SCAN_AS_OEM) != 0) {
pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
}
@@ -10130,9 +10225,10 @@ public class PackageManagerService extends IPackageManager.Stub
*
* @throws PackageManagerException If the package fails any of the validation checks
*/
- private void assertPackageIsValid(PackageParser.Package pkg, int policyFlags, int scanFlags)
- throws PackageManagerException {
- if ((policyFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {
+ private void assertPackageIsValid(PackageParser.Package pkg, final @ParseFlags int parseFlags,
+ final @ScanFlags int scanFlags)
+ throws PackageManagerException {
+ if ((parseFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {
assertCodePolicy(pkg);
}
@@ -10290,7 +10386,7 @@ public class PackageManagerService extends IPackageManager.Stub
// Only privileged apps and updated privileged apps can add child packages.
if (pkg.childPackages != null && !pkg.childPackages.isEmpty()) {
- if ((policyFlags & PARSE_IS_PRIVILEGED) == 0) {
+ if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
throw new PackageManagerException("Only privileged apps can add child "
+ "packages. Ignoring package " + pkg.packageName);
}
@@ -10416,7 +10512,8 @@ public class PackageManagerService extends IPackageManager.Stub
* be available for query, resolution, etc...
*/
private void commitPackageSettings(PackageParser.Package pkg, PackageSetting pkgSetting,
- UserHandle user, int scanFlags, boolean chatty) throws PackageManagerException {
+ UserHandle user, final @ScanFlags int scanFlags, boolean chatty)
+ throws PackageManagerException {
final String pkgName = pkg.packageName;
if (mCustomResolverComponentName != null &&
mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) {
@@ -10767,10 +10864,9 @@ public class PackageManagerService extends IPackageManager.Stub
*
* If {@code extractLibs} is true, native libraries are extracted from the app if required.
*/
- private static void derivePackageAbi(PackageParser.Package pkg, File scanFile,
- String cpuAbiOverride, boolean extractLibs,
- File appLib32InstallDir)
- throws PackageManagerException {
+ private static void derivePackageAbi(PackageParser.Package pkg, String cpuAbiOverride,
+ boolean extractLibs, File appLib32InstallDir)
+ throws PackageManagerException {
// Give ourselves some initial paths; we'll come back for another
// pass once we've determined ABI below.
setNativeLibraryPaths(pkg, appLib32InstallDir);
@@ -14903,7 +14999,12 @@ public class PackageManagerService extends IPackageManager.Stub
resourceFile = afterCodeFile;
// Reflect the rename in scanned details
- pkg.setCodePath(afterCodeFile.getAbsolutePath());
+ try {
+ pkg.setCodePath(afterCodeFile.getCanonicalPath());
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to get path: " + afterCodeFile, e);
+ return false;
+ }
pkg.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile,
afterCodeFile, pkg.baseCodePath));
pkg.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
@@ -15248,9 +15349,9 @@ public class PackageManagerService extends IPackageManager.Stub
/*
* Install a non-existing package.
*/
- private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,
- int scanFlags, UserHandle user, String installerPackageName, String volumeUuid,
- PackageInstalledInfo res, int installReason) {
+ private void installNewPackageLIF(PackageParser.Package pkg, final @ParseFlags int parseFlags,
+ final @ScanFlags int scanFlags, UserHandle user, String installerPackageName,
+ String volumeUuid, PackageInstalledInfo res, int installReason) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installNewPackage");
// Remember this for later, in case we need to rollback this install
@@ -15279,7 +15380,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
try {
- PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags,
+ PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags, scanFlags,
System.currentTimeMillis(), user);
updateSettingsLI(newPackage, installerPackageName, null, res, user, installReason);
@@ -15307,9 +15408,9 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- private void replacePackageLIF(PackageParser.Package pkg, final int policyFlags, int scanFlags,
- UserHandle user, String installerPackageName, PackageInstalledInfo res,
- int installReason) {
+ private void replacePackageLIF(PackageParser.Package pkg, final @ParseFlags int parseFlags,
+ final @ScanFlags int scanFlags, UserHandle user, String installerPackageName,
+ PackageInstalledInfo res, int installReason) {
final boolean isInstantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
final PackageParser.Package oldPackage;
@@ -15329,7 +15430,7 @@ public class PackageManagerService extends IPackageManager.Stub
== android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
if (oldTargetsPreRelease
&& !newTargetsPreRelease
- && ((policyFlags & PackageParser.PARSE_FORCE_SDK) == 0)) {
+ && ((parseFlags & PackageParser.PARSE_FORCE_SDK) == 0)) {
Slog.w(TAG, "Can't install package targeting released sdk");
res.setReturnCode(PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE);
return;
@@ -15480,15 +15581,16 @@ public class PackageManagerService extends IPackageManager.Stub
final boolean oem =
(oldPackage.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
- final int systemPolicyFlags = policyFlags
- | PackageParser.PARSE_IS_SYSTEM
- | (privileged ? PARSE_IS_PRIVILEGED : 0)
- | (oem ? PARSE_IS_OEM : 0);
+ final @ParseFlags int systemParseFlags = parseFlags;
+ final @ScanFlags int systemScanFlags = scanFlags
+ | SCAN_AS_SYSTEM
+ | (privileged ? SCAN_AS_PRIVILEGED : 0)
+ | (oem ? SCAN_AS_OEM : 0);
- replaceSystemPackageLIF(oldPackage, pkg, systemPolicyFlags, scanFlags,
+ replaceSystemPackageLIF(oldPackage, pkg, systemParseFlags, systemScanFlags,
user, allUsers, installerPackageName, res, installReason);
} else {
- replaceNonSystemPackageLIF(oldPackage, pkg, policyFlags, scanFlags,
+ replaceNonSystemPackageLIF(oldPackage, pkg, parseFlags, scanFlags,
user, allUsers, installerPackageName, res, installReason);
}
}
@@ -15510,9 +15612,9 @@ public class PackageManagerService extends IPackageManager.Stub
}
private void replaceNonSystemPackageLIF(PackageParser.Package deletedPackage,
- PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user,
- int[] allUsers, String installerPackageName, PackageInstalledInfo res,
- int installReason) {
+ PackageParser.Package pkg, final @ParseFlags int parseFlags,
+ final @ScanFlags int scanFlags, UserHandle user, int[] allUsers,
+ String installerPackageName, PackageInstalledInfo res, int installReason) {
if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old="
+ deletedPackage);
@@ -15553,7 +15655,7 @@ public class PackageManagerService extends IPackageManager.Stub
clearAppProfilesLIF(deletedPackage, UserHandle.USER_ALL);
try {
- final PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags,
+ final PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags,
scanFlags | SCAN_UPDATE_TIME, System.currentTimeMillis(), user);
updateSettingsLI(newPackage, installerPackageName, allUsers, res, user,
installReason);
@@ -15659,7 +15761,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
private void replaceSystemPackageLIF(PackageParser.Package deletedPackage,
- PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user,
+ PackageParser.Package pkg, final @ParseFlags int parseFlags,
+ final @ScanFlags int scanFlags, UserHandle user,
int[] allUsers, String installerPackageName, PackageInstalledInfo res,
int installReason) {
if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
@@ -15697,7 +15800,7 @@ public class PackageManagerService extends IPackageManager.Stub
PackageParser.Package newPackage = null;
try {
// Add the package to the internal data structures
- newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags, 0, user);
+ newPackage = scanPackageTracedLI(pkg, parseFlags, scanFlags, 0, user);
// Set the update and install times
PackageSetting deletedPkgSetting = (PackageSetting) deletedPackage.mExtras;
@@ -15754,7 +15857,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
// Add back the old system package
try {
- scanPackageTracedLI(deletedPackage, policyFlags, SCAN_UPDATE_SIGNATURE, 0, user);
+ scanPackageTracedLI(deletedPackage, parseFlags, SCAN_UPDATE_SIGNATURE, 0, user);
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to restore original package: " + e.getMessage());
}
@@ -16008,7 +16111,7 @@ public class PackageManagerService extends IPackageManager.Stub
final boolean virtualPreload =
((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
boolean replace = false;
- int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
+ @ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
if (args.move != null) {
// moving a complete application; perform an initial scan on the new install location
scanFlags |= SCAN_INITIAL;
@@ -16041,7 +16144,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
// Retrieve PackageSettings and parse package
- final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
+ @ParseFlags final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| PackageParser.PARSE_ENFORCE_CODE
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
| (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0)
@@ -16383,8 +16486,7 @@ public class PackageManagerService extends IPackageManager.Stub
String abiOverride = (TextUtils.isEmpty(pkg.cpuAbiOverride) ?
args.abiOverride : pkg.cpuAbiOverride);
final boolean extractNativeLibs = !pkg.isLibrary();
- derivePackageAbi(pkg, new File(pkg.codePath), abiOverride,
- extractNativeLibs, mAppLib32InstallDir);
+ derivePackageAbi(pkg, abiOverride, extractNativeLibs, mAppLib32InstallDir);
} catch (PackageManagerException pme) {
Slog.e(TAG, "Error deriving application ABI", pme);
res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI");
@@ -16473,7 +16575,7 @@ public class PackageManagerService extends IPackageManager.Stub
return;
}
}
- replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
+ replacePackageLIF(pkg, parseFlags, scanFlags, args.user,
installerPackageName, res, args.installReason);
} else {
installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
@@ -17099,7 +17201,7 @@ public class PackageManagerService extends IPackageManager.Stub
try (PackageFreezer freezer = freezePackageForDelete(packageName, freezeUser,
deleteFlags, "deletePackageX")) {
res = deletePackageLIF(packageName, UserHandle.of(removeUser), true, allUsers,
- deleteFlags | FLAGS_REMOVE_CHATTY, info, true, null);
+ deleteFlags | PackageManager.DELETE_CHATTY, info, true, null);
}
synchronized (mPackages) {
if (res) {
@@ -17291,7 +17393,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- removePackageLI(ps, (flags & FLAGS_REMOVE_CHATTY) != 0);
+ removePackageLI(ps, (flags & PackageManager.DELETE_CHATTY) != 0);
if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
final PackageParser.Package resolvedPkg;
@@ -17388,21 +17490,19 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- static boolean locationIsPrivileged(File path) {
+ static boolean locationIsPrivileged(String path) {
try {
- final String privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app")
- .getCanonicalPath();
- return path.getCanonicalPath().startsWith(privilegedAppDir);
+ final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
+ return path.startsWith(privilegedAppDir.getCanonicalPath());
} catch (IOException e) {
Slog.e(TAG, "Unable to access code path " + path);
}
return false;
}
- static boolean locationIsOem(File path) {
+ static boolean locationIsOem(String path) {
try {
- return path.getCanonicalPath().startsWith(
- Environment.getOemDirectory().getCanonicalPath());
+ return path.startsWith(Environment.getOemDirectory().getCanonicalPath());
} catch (IOException e) {
Slog.e(TAG, "Unable to access code path " + path);
}
@@ -17498,7 +17598,7 @@ public class PackageManagerService extends IPackageManager.Stub
// Install the system package
if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
try {
- installPackageFromSystemLIF(disabledPs.codePath, false /*isPrivileged*/, allUserHandles,
+ installPackageFromSystemLIF(disabledPs.codePathString, false, allUserHandles,
outInfo.origUsers, deletedPs.getPermissionsState(), writeSettings);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to restore system package:" + deletedPkg.packageName + ": "
@@ -17515,23 +17615,25 @@ public class PackageManagerService extends IPackageManager.Stub
/**
* Installs a package that's already on the system partition.
*/
- private PackageParser.Package installPackageFromSystemLIF(@NonNull File codePath,
+ private PackageParser.Package installPackageFromSystemLIF(@NonNull String codePathString,
boolean isPrivileged, @Nullable int[] allUserHandles, @Nullable int[] origUserHandles,
@Nullable PermissionsState origPermissionState, boolean writeSettings)
throws PackageManagerException {
- int parseFlags = mDefParseFlags
+ @ParseFlags int parseFlags =
+ mDefParseFlags
| PackageParser.PARSE_MUST_BE_APK
- | PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR;
- if (isPrivileged || locationIsPrivileged(codePath)) {
- parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
+ @ScanFlags int scanFlags = SCAN_AS_SYSTEM;
+ if (isPrivileged || locationIsPrivileged(codePathString)) {
+ scanFlags |= SCAN_AS_PRIVILEGED;
}
- if (locationIsOem(codePath)) {
- parseFlags |= PackageParser.PARSE_IS_OEM;
+ if (locationIsOem(codePathString)) {
+ scanFlags |= SCAN_AS_OEM;
}
+ final File codePath = new File(codePathString);
final PackageParser.Package pkg =
- scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null);
+ scanPackageTracedLI(codePath, parseFlags, scanFlags, 0 /*currentTime*/, null);
try {
// update shared libraries for the newly re-installed system package
@@ -19587,9 +19689,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
pp.setCallback(mPackageParserCallback);
final PackageParser.Package tmpPkg;
try {
- final int parseFlags = mDefParseFlags
+ final @ParseFlags int parseFlags = mDefParseFlags
| PackageParser.PARSE_MUST_BE_APK
- | PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR;
tmpPkg = pp.parsePackage(codePath, parseFlags);
} catch (PackageParserException e) {
@@ -19642,7 +19743,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
// until we can disable the package later.
enableSystemPackageLPw(deletedPkg);
}
- installPackageFromSystemLIF(new File(deletedPkg.codePath),
+ installPackageFromSystemLIF(deletedPkg.codePath,
false /*isPrivileged*/, null /*allUserHandles*/,
null /*origUserHandles*/, null /*origPermissionsState*/,
true /*writeSettings*/);
@@ -22607,6 +22708,12 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
@Override
+ public int getPackageUid(String packageName, int flags, int userId) {
+ return PackageManagerService.this
+ .getPackageUid(packageName, flags, userId);
+ }
+
+ @Override
public ApplicationInfo getApplicationInfo(
String packageName, int flags, int filterCallingUid, int userId) {
return PackageManagerService.this
diff --git a/com/android/server/pm/PackageManagerServiceUtils.java b/com/android/server/pm/PackageManagerServiceUtils.java
index 758abd76..20ec9b5e 100644
--- a/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/com/android/server/pm/PackageManagerServiceUtils.java
@@ -295,19 +295,20 @@ public class PackageManagerServiceUtils {
return false;
}
- public static long getLastModifiedTime(PackageParser.Package pkg, File srcFile) {
- if (srcFile.isDirectory()) {
- final File baseFile = new File(pkg.baseCodePath);
- long maxModifiedTime = baseFile.lastModified();
- if (pkg.splitCodePaths != null) {
- for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
- final File splitFile = new File(pkg.splitCodePaths[i]);
- maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
- }
+ public static long getLastModifiedTime(PackageParser.Package pkg) {
+ final File srcFile = new File(pkg.codePath);
+ if (!srcFile.isDirectory()) {
+ return srcFile.lastModified();
+ }
+ final File baseFile = new File(pkg.baseCodePath);
+ long maxModifiedTime = baseFile.lastModified();
+ if (pkg.splitCodePaths != null) {
+ for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
+ final File splitFile = new File(pkg.splitCodePaths[i]);
+ maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
}
- return maxModifiedTime;
}
- return srcFile.lastModified();
+ return maxModifiedTime;
}
/**
@@ -570,7 +571,7 @@ public class PackageManagerServiceUtils {
if (!match) {
throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Package " + packageName +
- " signatures don't match previously installed version; ignoring!");
+ " signatures do not match previously installed version; ignoring!");
}
}
// Check for shared user signatures
@@ -578,16 +579,16 @@ public class PackageManagerServiceUtils {
// Already existing package. Make sure signatures match
boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
parsedSignatures) == PackageManager.SIGNATURE_MATCH;
- if (!match) {
+ if (!match && compareCompat) {
match = matchSignaturesCompat(
packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
}
- if (!match && compareCompat) {
+ if (!match && compareRecover) {
match = matchSignaturesRecover(
packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures);
compatMatch |= match;
}
- if (!match && compareRecover) {
+ if (!match) {
throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
"Package " + packageName
+ " has no signatures that match those in shared user "
diff --git a/com/android/server/pm/PackageManagerShellCommand.java b/com/android/server/pm/PackageManagerShellCommand.java
index 807eb1a8..44f36d17 100644
--- a/com/android/server/pm/PackageManagerShellCommand.java
+++ b/com/android/server/pm/PackageManagerShellCommand.java
@@ -264,7 +264,7 @@ class PackageManagerShellCommand extends ShellCommand {
PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
null, null);
params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
- pkgLite, params.sessionParams.abiOverride));
+ pkgLite, params.sessionParams.abiOverride, fd.getFileDescriptor()));
} catch (PackageParserException | IOException e) {
getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
throw new IllegalArgumentException(
@@ -1169,11 +1169,17 @@ class PackageManagerShellCommand extends ShellCommand {
}
List<String> failedPackages = new ArrayList<>();
+ int index = 0;
for (String packageName : packageNames) {
if (clearProfileData) {
mInterface.clearApplicationProfileData(packageName);
}
+ if (allPackages) {
+ pw.println(++index + "/" + packageNames.size() + ": " + packageName);
+ pw.flush();
+ }
+
boolean result = secondaryDex
? mInterface.performDexOptSecondary(packageName,
targetCompilerFilter, forceCompilation)
@@ -1219,7 +1225,13 @@ class PackageManagerShellCommand extends ShellCommand {
}
private int runDexoptJob() throws RemoteException {
- boolean result = mInterface.runBackgroundDexoptJob();
+ String arg;
+ List<String> packageNames = new ArrayList<>();
+ while ((arg = getNextArg()) != null) {
+ packageNames.add(arg);
+ }
+ boolean result = mInterface.runBackgroundDexoptJob(packageNames.isEmpty() ? null :
+ packageNames);
return result ? 0 : -1;
}
diff --git a/com/android/server/pm/Settings.java b/com/android/server/pm/Settings.java
index 7077fd16..ddad6774 100644
--- a/com/android/server/pm/Settings.java
+++ b/com/android/server/pm/Settings.java
@@ -3572,11 +3572,10 @@ public final class Settings {
int pkgFlags = 0;
int pkgPrivateFlags = 0;
pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
- final File codePathFile = new File(codePathStr);
- if (PackageManagerService.locationIsPrivileged(codePathFile)) {
+ if (PackageManagerService.locationIsPrivileged(codePathStr)) {
pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
- PackageSetting ps = new PackageSetting(name, realName, codePathFile,
+ PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr,
secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags, pkgPrivateFlags,
parentPackageName, null /*childPackageNames*/, 0 /*sharedUserId*/, null, null);
diff --git a/com/android/server/pm/UserManagerService.java b/com/android/server/pm/UserManagerService.java
index 11523101..dbf413f0 100644
--- a/com/android/server/pm/UserManagerService.java
+++ b/com/android/server/pm/UserManagerService.java
@@ -717,6 +717,19 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ @Override
+ public int getProfileParentId(int userHandle) {
+ checkManageUsersPermission("get the profile parent");
+ synchronized (mUsersLock) {
+ UserInfo profileParent = getProfileParentLU(userHandle);
+ if (profileParent == null) {
+ return userHandle;
+ }
+
+ return profileParent.id;
+ }
+ }
+
private UserInfo getProfileParentLU(int userHandle) {
UserInfo profile = getUserInfoLU(userHandle);
if (profile == null) {
diff --git a/com/android/server/pm/dex/DexLogger.java b/com/android/server/pm/dex/DexLogger.java
new file mode 100644
index 00000000..88d9e52c
--- /dev/null
+++ b/com/android/server/pm/dex/DexLogger.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm.dex;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.os.RemoteException;
+
+import android.util.ArraySet;
+import android.util.ByteStringUtils;
+import android.util.EventLog;
+import android.util.PackageUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
+
+import java.io.File;
+import java.util.Set;
+
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+
+/**
+ * This class is responsible for logging data about secondary dex files.
+ * The data logged includes hashes of the name and content of each file.
+ */
+public class DexLogger implements DexManager.Listener {
+ private static final String TAG = "DexLogger";
+
+ // Event log tag & subtag used for SafetyNet logging of dynamic
+ // code loading (DCL) - see b/63927552.
+ private static final int SNET_TAG = 0x534e4554;
+ private static final String DCL_SUBTAG = "dcl";
+
+ private final IPackageManager mPackageManager;
+ private final Object mInstallLock;
+ @GuardedBy("mInstallLock")
+ private final Installer mInstaller;
+
+ public static DexManager.Listener getListener(IPackageManager pms,
+ Installer installer, Object installLock) {
+ return new DexLogger(pms, installer, installLock);
+ }
+
+ @VisibleForTesting
+ /*package*/ DexLogger(IPackageManager pms, Installer installer, Object installLock) {
+ mPackageManager = pms;
+ mInstaller = installer;
+ mInstallLock = installLock;
+ }
+
+ /**
+ * Compute and log hashes of the name and content of a secondary dex file.
+ */
+ @Override
+ public void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
+ String dexPath, int storageFlags) {
+ int ownerUid = appInfo.uid;
+
+ byte[] hash = null;
+ synchronized(mInstallLock) {
+ try {
+ hash = mInstaller.hashSecondaryDexFile(dexPath, appInfo.packageName,
+ ownerUid, appInfo.volumeUuid, storageFlags);
+ } catch (InstallerException e) {
+ Slog.e(TAG, "Got InstallerException when hashing dex " + dexPath +
+ " : " + e.getMessage());
+ }
+ }
+ if (hash == null) {
+ return;
+ }
+
+ String dexFileName = new File(dexPath).getName();
+ String message = PackageUtils.computeSha256Digest(dexFileName.getBytes());
+ // Valid SHA256 will be 256 bits, 32 bytes.
+ if (hash.length == 32) {
+ message = message + ' ' + ByteStringUtils.toHexString(hash);
+ }
+
+ writeDclEvent(ownerUid, message);
+
+ if (dexUseInfo.isUsedByOtherApps()) {
+ Set<String> otherPackages = dexUseInfo.getLoadingPackages();
+ Set<Integer> otherUids = new ArraySet<>(otherPackages.size());
+ for (String otherPackageName : otherPackages) {
+ try {
+ int otherUid = mPackageManager.getPackageUid(
+ otherPackageName, /*flags*/0, dexUseInfo.getOwnerUserId());
+ if (otherUid != -1 && otherUid != ownerUid) {
+ otherUids.add(otherUid);
+ }
+ } catch (RemoteException ignore) {
+ // Can't happen, we're local.
+ }
+ }
+ for (int otherUid : otherUids) {
+ writeDclEvent(otherUid, message);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ /*package*/ void writeDclEvent(int uid, String message) {
+ EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message);
+ }
+}
diff --git a/com/android/server/pm/dex/DexManager.java b/com/android/server/pm/dex/DexManager.java
index 62747547..0e2730cb 100644
--- a/com/android/server/pm/dex/DexManager.java
+++ b/com/android/server/pm/dex/DexManager.java
@@ -76,6 +76,7 @@ public class DexManager {
private final Object mInstallLock;
@GuardedBy("mInstallLock")
private final Installer mInstaller;
+ private final Listener mListener;
// Possible outcomes of a dex search.
private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found
@@ -96,14 +97,24 @@ public class DexManager {
*/
private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
+ public interface Listener {
+ /**
+ * Invoked just before the secondary dex file {@code dexPath} for the specified application
+ * is reconciled.
+ */
+ void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
+ String dexPath, int storageFlags);
+ }
+
public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
- Installer installer, Object installLock) {
+ Installer installer, Object installLock, Listener listener) {
mPackageCodeLocationsCache = new HashMap<>();
mPackageDexUsage = new PackageDexUsage();
mPackageManager = pms;
mPackageDexOptimizer = pdo;
mInstaller = installer;
mInstallLock = installLock;
+ mListener = listener;
}
/**
@@ -389,7 +400,7 @@ public class DexManager {
: mPackageDexOptimizer;
String packageName = options.getPackageName();
PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
- if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+ if (useInfo.getDexUseInfoMap().isEmpty()) {
if (DEBUG) {
Slog.d(TAG, "No secondary dex use for package:" + packageName);
}
@@ -433,7 +444,7 @@ public class DexManager {
*/
public void reconcileSecondaryDexFiles(String packageName) {
PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
- if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+ if (useInfo.getDexUseInfoMap().isEmpty()) {
if (DEBUG) {
Slog.d(TAG, "No secondary dex use for package:" + packageName);
}
@@ -481,12 +492,16 @@ public class DexManager {
continue;
}
+ if (mListener != null) {
+ mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags);
+ }
+
boolean dexStillExists = true;
synchronized(mInstallLock) {
try {
String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
- pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
+ info.uid, isas, info.volumeUuid, flags);
} catch (InstallerException e) {
Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
" : " + e.getMessage());
diff --git a/com/android/server/policy/BarController.java b/com/android/server/policy/BarController.java
index b1792358..10d9565c 100644
--- a/com/android/server/policy/BarController.java
+++ b/com/android/server/policy/BarController.java
@@ -24,9 +24,9 @@ import android.util.Slog;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy.WindowState;
import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy.WindowState;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.PrintWriter;
diff --git a/com/android/server/policy/GlobalActions.java b/com/android/server/policy/GlobalActions.java
index 3707a5ed..108b6b25 100644
--- a/com/android/server/policy/GlobalActions.java
+++ b/com/android/server/policy/GlobalActions.java
@@ -14,14 +14,14 @@
package com.android.server.policy;
-import com.android.server.LocalServices;
-import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener;
-
import android.content.Context;
import android.os.Handler;
import android.util.Slog;
-import android.view.WindowManagerPolicy.WindowManagerFuncs;
+
+import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener;
class GlobalActions implements GlobalActionsListener {
diff --git a/com/android/server/policy/LegacyGlobalActions.java b/com/android/server/policy/LegacyGlobalActions.java
index 8eb6d065..96d062df 100644
--- a/com/android/server/policy/LegacyGlobalActions.java
+++ b/com/android/server/policy/LegacyGlobalActions.java
@@ -25,6 +25,7 @@ import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.R;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
import android.app.ActivityManager;
import android.app.Dialog;
@@ -64,7 +65,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.WindowManagerPolicy.WindowManagerFuncs;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
@@ -919,7 +919,7 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn
/**
* @param enabledIconResId The icon for when this action is on.
* @param disabledIconResid The icon for when this action is off.
- * @param essage The general information message, e.g 'Silent Mode'
+ * @param message The general information message, e.g 'Silent Mode'
* @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
* @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
*/
diff --git a/com/android/server/policy/PhoneWindowManager.java b/com/android/server/policy/PhoneWindowManager.java
index 9162a97a..7415ec38 100644
--- a/com/android/server/policy/PhoneWindowManager.java
+++ b/com/android/server/policy/PhoneWindowManager.java
@@ -119,12 +119,13 @@ import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
+
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -210,7 +211,6 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
-import android.view.DisplayFrames;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.IApplicationToken;
@@ -230,9 +230,6 @@ import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
-import android.view.WindowManagerInternal;
-import android.view.WindowManagerInternal.AppTransitionListener;
-import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Animation;
@@ -243,6 +240,7 @@ import android.view.inputmethod.InputMethodManagerInternal;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IShortcutService;
@@ -259,6 +257,9 @@ import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.AppTransition;
+import com.android.server.wm.DisplayFrames;
+import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal.AppTransitionListener;
import java.io.File;
import java.io.FileReader;
@@ -304,6 +305,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
static final int LONG_PRESS_POWER_SHUT_OFF = 2;
static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3;
+ static final int LONG_PRESS_POWER_GO_TO_VOICE_ASSIST = 4;
+
+ static final int VERY_LONG_PRESS_POWER_NOTHING = 0;
+ static final int VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
static final int MULTI_PRESS_POWER_NOTHING = 0;
static final int MULTI_PRESS_POWER_THEATER_MODE = 1;
@@ -569,6 +574,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
boolean mLidControlsSleep;
int mShortPressOnPowerBehavior;
int mLongPressOnPowerBehavior;
+ int mVeryLongPressOnPowerBehavior;
int mDoublePressOnPowerBehavior;
int mTriplePressOnPowerBehavior;
int mLongPressOnBackBehavior;
@@ -586,6 +592,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
boolean mHasSoftInput = false;
boolean mTranslucentDecorEnabled = true;
boolean mUseTvRouting;
+ int mVeryLongPressTimeout;
private boolean mHandleVolumeKeysInWM;
@@ -796,6 +803,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
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_POWER_VERY_LONG_PRESS = 29;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -855,6 +863,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case MSG_POWER_LONG_PRESS:
powerLongPress();
break;
+ case MSG_POWER_VERY_LONG_PRESS:
+ powerVeryLongPress();
+ break;
case MSG_UPDATE_DREAMING_SLEEP_TOKEN:
updateDreamingSleepToken(msg.arg1 != 0);
break;
@@ -1043,7 +1054,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private ImmersiveModeConfirmation mImmersiveModeConfirmation;
- private SystemGesturesPointerEventListener mSystemGestures;
+ @VisibleForTesting
+ SystemGesturesPointerEventListener mSystemGestures;
IStatusBarService getStatusBarService() {
synchronized (mServiceAquireLock) {
@@ -1299,6 +1311,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+
+ if (hasVeryLongPressOnPowerBehavior()) {
+ Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
+ longMsg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
+ }
}
} else {
wakeUpFromPowerKey(event.getDownTime());
@@ -1308,6 +1326,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+
+ if (hasVeryLongPressOnPowerBehavior()) {
+ Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
+ longMsg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
+ }
+
mBeganFromNonInteractive = true;
} else {
final int maxCount = getMaxMultiPressPowerCount();
@@ -1369,6 +1394,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mPowerKeyHandled = true;
mHandler.removeMessages(MSG_POWER_LONG_PRESS);
}
+ if (hasVeryLongPressOnPowerBehavior()) {
+ mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS);
+ }
}
private void cancelPendingBackKeyAction() {
@@ -1516,6 +1544,29 @@ public class PhoneWindowManager implements WindowManagerPolicy {
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
break;
+ case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
+ mPowerKeyHandled = true;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ final boolean keyguardActive = mKeyguardDelegate == null
+ ? false
+ : mKeyguardDelegate.isShowing();
+ if (!keyguardActive) {
+ Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+ startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ }
+ break;
+ }
+ }
+
+ private void powerVeryLongPress() {
+ switch (mVeryLongPressOnPowerBehavior) {
+ case VERY_LONG_PRESS_POWER_NOTHING:
+ break;
+ case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
+ mPowerKeyHandled = true;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ showGlobalActionsInternal();
+ break;
}
}
@@ -1574,6 +1625,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING;
}
+ private boolean hasVeryLongPressOnPowerBehavior() {
+ return mVeryLongPressOnPowerBehavior != VERY_LONG_PRESS_POWER_NOTHING;
+ }
+
private boolean hasLongPressOnBackBehavior() {
return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING;
}
@@ -1979,12 +2034,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
com.android.internal.R.integer.config_shortPressOnPowerBehavior);
mLongPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
+ mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressOnPowerBehavior);
mDoublePressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_doublePressOnPowerBehavior);
mTriplePressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_triplePressOnPowerBehavior);
mShortPressOnSleepBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_shortPressOnSleepBehavior);
+ mVeryLongPressTimeout = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressTimeout);
mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION;
@@ -2607,17 +2666,21 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// The status bar is the only window allowed to exhibit keyguard behavior.
attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
}
+ }
+ private int getImpliedSysUiFlagsForLayout(LayoutParams attrs) {
+ int impliedFlags = 0;
if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
- attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
}
final boolean forceWindowDrawsStatusBarBackground =
(attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
|| forceWindowDrawsStatusBarBackground
&& attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
- attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
}
+ return impliedFlags;
}
void readLidState() {
@@ -2667,7 +2730,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public void onConfigurationChanged() {
// TODO(multi-display): Define policy for secondary displays.
- Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
+ Context uiContext = getSystemUiContext();
final Resources res = uiContext.getResources();
mStatusBarHeight =
@@ -2708,6 +2771,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
+ @VisibleForTesting
+ Context getSystemUiContext() {
+ return ActivityThread.currentActivityThread().getSystemUiContext();
+ }
+
@Override
public int getMaxWallpaperLayer() {
return getWindowLayerFromTypeLw(TYPE_STATUS_BAR);
@@ -4768,7 +4836,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
final int fl = PolicyControl.getWindowFlags(win, attrs);
final int pfl = attrs.privateFlags;
final int sim = attrs.softInputMode;
- final int sysUiFl = PolicyControl.getSystemUiVisibility(win, null);
+ final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null);
+ final int sysUiFl = requestedSysUiFl | getImpliedSysUiFlagsForLayout(attrs);
final Rect pf = mTmpParentFrame;
final Rect df = mTmpDisplayFrame;
@@ -8062,19 +8131,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
@Override
- public boolean canMagnifyWindow(int windowType) {
- switch (windowType) {
- case WindowManager.LayoutParams.TYPE_INPUT_METHOD:
- case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG:
- case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR:
- case WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY: {
- return false;
- }
- }
- return true;
- }
-
- @Override
public boolean isTopLevelWindow(int windowType) {
if (windowType >= WindowManager.LayoutParams.FIRST_SUB_WINDOW
&& windowType <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
@@ -8206,6 +8262,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pw.print("mLongPressOnPowerBehavior=");
pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
pw.print(prefix);
+ pw.print("mVeryLongPressOnPowerBehavior=");
+ pw.println(veryLongPressOnPowerBehaviorToString(mVeryLongPressOnPowerBehavior));
+ pw.print(prefix);
pw.print("mDoublePressOnPowerBehavior=");
pw.println(multiPressOnPowerBehaviorToString(mDoublePressOnPowerBehavior));
pw.print(prefix);
@@ -8458,6 +8517,18 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return Integer.toString(behavior);
}
}
+
+ private static String veryLongPressOnPowerBehaviorToString(int behavior) {
+ switch (behavior) {
+ case VERY_LONG_PRESS_POWER_NOTHING:
+ return "VERY_LONG_PRESS_POWER_NOTHING";
+ case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
+ return "VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
private static String multiPressOnPowerBehaviorToString(int behavior) {
switch (behavior) {
case MULTI_PRESS_POWER_NOTHING:
diff --git a/com/android/server/policy/PolicyControl.java b/com/android/server/policy/PolicyControl.java
index dbafc424..3f26d867 100644
--- a/com/android/server/policy/PolicyControl.java
+++ b/com/android/server/policy/PolicyControl.java
@@ -25,7 +25,8 @@ import android.util.Slog;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerPolicy.WindowState;
+
+import com.android.server.policy.WindowManagerPolicy.WindowState;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -36,7 +37,7 @@ import java.io.StringWriter;
* This includes forcing immersive mode behavior for one or both system bars (based on a package
* list) and permanently disabling immersive mode confirmations for specific packages.
*
- * Control by setting {@link Settings.Global.POLICY_CONTROL} to one or more name-value pairs.
+ * Control by setting {@link Settings.Global#POLICY_CONTROL} to one or more name-value pairs.
* e.g.
* to force immersive mode everywhere:
* "immersive.full=*"
diff --git a/com/android/server/policy/SplashScreenSurface.java b/com/android/server/policy/SplashScreenSurface.java
index 37d6c0b5..b9202c33 100644
--- a/com/android/server/policy/SplashScreenSurface.java
+++ b/com/android/server/policy/SplashScreenSurface.java
@@ -23,11 +23,10 @@ import android.os.IBinder;
import android.util.Slog;
import android.view.View;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
-import android.view.WindowManagerPolicy.StartingSurface;
import com.android.internal.policy.DecorView;
import com.android.internal.policy.PhoneWindow;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
/**
* Holds the contents of a splash screen starting window, i.e. the {@link DecorView} of a
diff --git a/com/android/server/policy/StatusBarController.java b/com/android/server/policy/StatusBarController.java
index ecc88b50..af7e91c5 100644
--- a/com/android/server/policy/StatusBarController.java
+++ b/com/android/server/policy/StatusBarController.java
@@ -18,7 +18,7 @@ package com.android.server.policy;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManagerInternal.AppTransitionListener;
+import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
import android.app.StatusBarManager;
import android.os.IBinder;
diff --git a/com/android/server/policy/SystemGesturesPointerEventListener.java b/com/android/server/policy/SystemGesturesPointerEventListener.java
index 598c58e5..d3cc8eff 100644
--- a/com/android/server/policy/SystemGesturesPointerEventListener.java
+++ b/com/android/server/policy/SystemGesturesPointerEventListener.java
@@ -24,7 +24,7 @@ import android.util.Slog;
import android.view.GestureDetector;
import android.view.InputDevice;
import android.view.MotionEvent;
-import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.widget.OverScroller;
/*
diff --git a/android/view/WindowManagerPolicy.java b/com/android/server/policy/WindowManagerPolicy.java
index 534335bf..5f067d49 100644
--- a/android/view/WindowManagerPolicy.java
+++ b/com/android/server/policy/WindowManagerPolicy.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 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,10 +14,8 @@
* limitations under the License.
*/
-package android.view;
+package com.android.server.policy;
-import static android.Manifest.permission;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
@@ -63,9 +61,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.Nullable;
-import android.annotation.SystemApi;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
@@ -78,10 +76,20 @@ import android.os.Looper;
import android.os.RemoteException;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import android.view.IApplicationToken;
+import android.view.IWindowManager;
+import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.Surface;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerPolicyConstants;
import android.view.animation.Animation;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IShortcutService;
+import com.android.server.wm.DisplayFrames;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -125,62 +133,26 @@ import java.lang.annotation.RetentionPolicy;
* acquired by the window manager while it holds the window lock, so this is
* even more restrictive than <var>Lw</var>.
* </dl>
- *
- * @hide
*/
-public interface WindowManagerPolicy {
- // Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h.
- public final static int FLAG_WAKE = 0x00000001;
- public final static int FLAG_VIRTUAL = 0x00000002;
-
- public final static int FLAG_INJECTED = 0x01000000;
- public final static int FLAG_TRUSTED = 0x02000000;
- public final static int FLAG_FILTERED = 0x04000000;
- public final static int FLAG_DISABLE_KEY_REPEAT = 0x08000000;
-
- public final static int FLAG_INTERACTIVE = 0x20000000;
- public final static int FLAG_PASS_TO_USER = 0x40000000;
-
- // Flags for IActivityManager.keyguardGoingAway()
- public final static int KEYGUARD_GOING_AWAY_FLAG_TO_SHADE = 1 << 0;
- public final static int KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS = 1 << 1;
- public final static int KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER = 1 << 2;
-
- // Flags used for indicating whether the internal and/or external input devices
- // of some type are available.
- public final static int PRESENCE_INTERNAL = 1 << 0;
- public final static int PRESENCE_EXTERNAL = 1 << 1;
-
+public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
// Navigation bar position values
int NAV_BAR_LEFT = 1 << 0;
int NAV_BAR_RIGHT = 1 << 1;
int NAV_BAR_BOTTOM = 1 << 2;
- public final static boolean WATCH_POINTER = false;
-
- /**
- * Sticky broadcast of the current HDMI plugged state.
- */
- public final static String ACTION_HDMI_PLUGGED = "android.intent.action.HDMI_PLUGGED";
-
- /**
- * Extra in {@link #ACTION_HDMI_PLUGGED} indicating the state: true if
- * plugged in to HDMI, false if not.
- */
- public final static String EXTRA_HDMI_PLUGGED_STATE = "state";
-
- /**
- * Set to {@code true} when intent was invoked from pressing the home key.
- * @hide
- */
- @SystemApi
- public static final String EXTRA_FROM_HOME_KEY = "android.intent.extra.FROM_HOME_KEY";
-
/**
* Pass this event to the user / app. To be returned from
* {@link #interceptKeyBeforeQueueing}.
*/
- public final static int ACTION_PASS_TO_USER = 0x00000001;
+ int ACTION_PASS_TO_USER = 0x00000001;
+ /** Layout state may have changed (so another layout will be performed) */
+ int FINISH_LAYOUT_REDO_LAYOUT = 0x0001;
+ /** Configuration state may have changed */
+ int FINISH_LAYOUT_REDO_CONFIG = 0x0002;
+ /** Wallpaper may need to move */
+ int FINISH_LAYOUT_REDO_WALLPAPER = 0x0004;
+ /** Need to recompute animations */
+ int FINISH_LAYOUT_REDO_ANIM = 0x0008;
/**
* Register shortcuts for window manager to dispatch.
@@ -241,7 +213,7 @@ public interface WindowManagerPolicy {
*/
public void computeFrameLw(Rect parentFrame, Rect displayFrame,
Rect overlayFrame, Rect contentFrame, Rect visibleFrame, Rect decorFrame,
- Rect stableFrame, Rect outsetFrame);
+ Rect stableFrame, @Nullable Rect outsetFrame);
/**
* Retrieve the current frame of the window that has been assigned by
@@ -483,7 +455,7 @@ public interface WindowManagerPolicy {
/**
* Returns true if the window owner can add internal system windows.
- * That is, they have {@link permission#INTERNAL_SYSTEM_WINDOW}.
+ * That is, they have {@link Manifest.permission#INTERNAL_SYSTEM_WINDOW}.
*/
default boolean canAddInternalSystemWindow() {
return false;
@@ -491,7 +463,7 @@ public interface WindowManagerPolicy {
/**
* Returns true if the window owner has the permission to acquire a sleep token when it's
- * visible. That is, they have the permission {@link permission#DEVICE_POWER}.
+ * visible. That is, they have the permission {@link Manifest.permission#DEVICE_POWER}.
*/
boolean canAcquireSleepToken();
}
@@ -648,24 +620,6 @@ public interface WindowManagerPolicy {
}
}
- public interface PointerEventListener {
- /**
- * 1. onPointerEvent will be called on the service.UiThread.
- * 2. motionEvent will be recycled after onPointerEvent returns so if it is needed later a
- * copy() must be made and the copy must be recycled.
- **/
- void onPointerEvent(MotionEvent motionEvent);
-
- /**
- * @see #onPointerEvent(MotionEvent)
- **/
- default void onPointerEvent(MotionEvent motionEvent, int displayId) {
- if (displayId == DEFAULT_DISPLAY) {
- onPointerEvent(motionEvent);
- }
- }
- }
-
/** Window has been added to the screen. */
public static final int TRANSIT_ENTER = 1;
/** Window has been removed from the screen. */
@@ -682,13 +636,6 @@ public interface WindowManagerPolicy {
// NOTE: screen off reasons are in order of significance, with more
// important ones lower than less important ones.
- /** Screen turned off because of a device admin */
- public final int OFF_BECAUSE_OF_ADMIN = 1;
- /** Screen turned off because of power button */
- public final int OFF_BECAUSE_OF_USER = 2;
- /** Screen turned off because of timeout */
- public final int OFF_BECAUSE_OF_TIMEOUT = 3;
-
/** @hide */
@IntDef({USER_ROTATION_FREE, USER_ROTATION_LOCKED})
@Retention(RetentionPolicy.SOURCE)
@@ -806,7 +753,7 @@ public interface WindowManagerPolicy {
* @param type The type of window being assigned.
* @param canAddInternalSystemWindow If the owner window associated with the type we are
* evaluating can add internal system windows. I.e they have
- * {@link permission#INTERNAL_SYSTEM_WINDOW}. If true, alert window
+ * {@link Manifest.permission#INTERNAL_SYSTEM_WINDOW}. If true, alert window
* types {@link android.view.WindowManager.LayoutParams#isSystemAlertWindowType(int)}
* can be assigned layers greater than the layer for
* {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} Else, their
@@ -861,11 +808,11 @@ public interface WindowManagerPolicy {
case TYPE_INPUT_METHOD_DIALOG:
// on-screen keyboards and other such input method user interfaces go here.
return 15;
- case TYPE_STATUS_BAR_SUB_PANEL:
- return 17;
case TYPE_STATUS_BAR:
- return 18;
+ return 17;
case TYPE_STATUS_BAR_PANEL:
+ return 18;
+ case TYPE_STATUS_BAR_SUB_PANEL:
return 19;
case TYPE_KEYGUARD_DIALOG:
return 20;
@@ -916,13 +863,6 @@ public interface WindowManagerPolicy {
}
}
- int APPLICATION_LAYER = 2;
- int APPLICATION_MEDIA_SUBLAYER = -2;
- int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
- int APPLICATION_PANEL_SUBLAYER = 1;
- int APPLICATION_SUB_PANEL_SUBLAYER = 2;
- int APPLICATION_ABOVE_SUB_PANEL_SUBLAYER = 3;
-
/**
* Return how to Z-order sub-windows in relation to the window they are attached to.
* Return positive to have them ordered in front, negative for behind.
@@ -975,7 +915,7 @@ public interface WindowManagerPolicy {
/**
* Return the available screen width that we should report for the
* configuration. This must be no larger than
- * {@link #getNonDecorDisplayWidth(int, int, int)}; it may be smaller than
+ * {@link #getNonDecorDisplayWidth(int, int, int, int int, int)}; it may be smaller than
* that to account for more transient decoration like a status bar.
*/
public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation,
@@ -984,7 +924,7 @@ public interface WindowManagerPolicy {
/**
* Return the available screen height that we should report for the
* configuration. This must be no larger than
- * {@link #getNonDecorDisplayHeight(int, int, int)}; it may be smaller than
+ * {@link #getNonDecorDisplayHeight(int, int, int, int, int)}; it may be smaller than
* that to account for more transient decoration like a status bar.
*/
public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation,
@@ -1213,15 +1153,6 @@ public interface WindowManagerPolicy {
return false;
}
- /** Layout state may have changed (so another layout will be performed) */
- static final int FINISH_LAYOUT_REDO_LAYOUT = 0x0001;
- /** Configuration state may have changed */
- static final int FINISH_LAYOUT_REDO_CONFIG = 0x0002;
- /** Wallpaper may need to move */
- static final int FINISH_LAYOUT_REDO_WALLPAPER = 0x0004;
- /** Need to recompute animations */
- static final int FINISH_LAYOUT_REDO_ANIM = 0x0008;
-
/**
* Called following layout of all windows before each window has policy applied.
*
@@ -1374,7 +1305,7 @@ public interface WindowManagerPolicy {
public void enableKeyguard(boolean enabled);
/**
- * Callback used by {@link WindowManagerPolicy#exitKeyguardSecurely}
+ * Callback used by {@link #exitKeyguardSecurely}
*/
interface OnKeyguardExitResult {
void onKeyguardExitResult(boolean success);
@@ -1552,8 +1483,8 @@ public interface WindowManagerPolicy {
*
* @return The rotation mode.
*
- * @see WindowManagerPolicy#USER_ROTATION_LOCKED
- * @see WindowManagerPolicy#USER_ROTATION_FREE
+ * @see #USER_ROTATION_LOCKED
+ * @see #USER_ROTATION_FREE
*/
@UserRotationMode
public int getUserRotationMode();
@@ -1561,8 +1492,7 @@ public interface WindowManagerPolicy {
/**
* Inform the policy that the user has chosen a preferred orientation ("rotation lock").
*
- * @param mode One of {@link WindowManagerPolicy#USER_ROTATION_LOCKED} or
- * {@link WindowManagerPolicy#USER_ROTATION_FREE}.
+ * @param mode One of {@link #USER_ROTATION_LOCKED} or {@link #USER_ROTATION_FREE}.
* @param rotation One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
* {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
*/
@@ -1663,14 +1593,6 @@ public interface WindowManagerPolicy {
void writeToProto(ProtoOutputStream proto, long fieldId);
/**
- * Returns whether a given window type can be magnified.
- *
- * @param windowType The window type.
- * @return True if the window can be magnified.
- */
- public boolean canMagnifyWindow(int windowType);
-
- /**
* Returns whether a given window type is considered a top level one.
* A top level window does not have a container, i.e. attached window,
* or if it has a container it is laid out as a top-level window, not
@@ -1765,20 +1687,4 @@ public interface WindowManagerPolicy {
return Integer.toString(mode);
}
}
-
- /**
- * Convert the off reason to a human readable format.
- */
- static String offReasonToString(int why) {
- switch (why) {
- case OFF_BECAUSE_OF_ADMIN:
- return "OFF_BECAUSE_OF_ADMIN";
- case OFF_BECAUSE_OF_USER:
- return "OFF_BECAUSE_OF_USER";
- case OFF_BECAUSE_OF_TIMEOUT:
- return "OFF_BECAUSE_OF_TIMEOUT";
- default:
- return Integer.toString(why);
- }
- }
}
diff --git a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 70cd54ff..58002bc8 100644
--- a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -15,14 +15,14 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
-import android.view.WindowManagerPolicy;
-import android.view.WindowManagerPolicy.OnKeyguardExitResult;
+import android.view.WindowManagerPolicyConstants;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IKeyguardDrawnCallback;
import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardService;
import com.android.server.UiThread;
+import com.android.server.policy.WindowManagerPolicy.OnKeyguardExitResult;
import java.io.PrintWriter;
@@ -419,7 +419,7 @@ public class KeyguardServiceDelegate {
pw.println(prefix + "deviceHasKeyguard=" + mKeyguardState.deviceHasKeyguard);
pw.println(prefix + "enabled=" + mKeyguardState.enabled);
pw.println(prefix + "offReason=" +
- WindowManagerPolicy.offReasonToString(mKeyguardState.offReason));
+ WindowManagerPolicyConstants.offReasonToString(mKeyguardState.offReason));
pw.println(prefix + "currentUser=" + mKeyguardState.currentUser);
pw.println(prefix + "bootCompleted=" + mKeyguardState.bootCompleted);
pw.println(prefix + "screenState=" + screenStateToString(mKeyguardState.screenState));
diff --git a/com/android/server/power/BatterySaverPolicy.java b/com/android/server/power/BatterySaverPolicy.java
index 15121b80..7c234f96 100644
--- a/com/android/server/power/BatterySaverPolicy.java
+++ b/com/android/server/power/BatterySaverPolicy.java
@@ -29,21 +29,26 @@ import android.util.ArrayMap;
import android.util.KeyValueListParser;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.R;
+import com.android.server.power.batterysaver.CpuFrequencies;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
/**
* Class to decide whether to turn on battery saver mode for specific service
*
- * Test: atest BatterySaverPolicyTest
+ * Test:
+ atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
*/
public class BatterySaverPolicy extends ContentObserver {
private static final String TAG = "BatterySaverPolicy";
+ public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE.
+
// Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode.
public static final int GPS_MODE_NO_CHANGE = 0;
// Value of batterySaverGpsMode such that GPS is disabled when battery saver mode
@@ -59,19 +64,27 @@ public class BatterySaverPolicy extends ContentObserver {
private static final String KEY_FIREWALL_DISABLED = "firewall_disabled";
private static final String KEY_ADJUST_BRIGHTNESS_DISABLED = "adjust_brightness_disabled";
private static final String KEY_DATASAVER_DISABLED = "datasaver_disabled";
+ private static final String KEY_LAUNCH_BOOST_DISABLED = "launch_boost_disabled";
private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor";
private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
- private static final String KEY_FORCE_ALL_APPS_STANDBY_JOBS = "force_all_apps_standby_jobs";
- private static final String KEY_FORCE_ALL_APPS_STANDBY_ALARMS = "force_all_apps_standby_alarms";
+ private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby";
+ private static final String KEY_FORCE_BACKGROUND_CHECK = "force_background_check";
private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
- private static final String KEY_SCREEN_ON_FILE_PREFIX = "file-on:";
- private static final String KEY_SCREEN_OFF_FILE_PREFIX = "file-off:";
+ private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i";
+ private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n";
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private String mSettings;
+
+ @GuardedBy("mLock")
+ private String mDeviceSpecificSettings;
- private static String mSettings;
- private static String mDeviceSpecificSettings;
- private static String mDeviceSpecificSettingsSource; // For dump() only.
+ @GuardedBy("mLock")
+ private String mDeviceSpecificSettingsSource; // For dump() only.
/**
* {@code true} if vibration is disabled in battery saver mode.
@@ -79,6 +92,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_VIBRATION_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mVibrationDisabled;
/**
@@ -87,6 +101,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ANIMATION_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mAnimationDisabled;
/**
@@ -96,6 +111,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_SOUNDTRIGGER_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mSoundTriggerDisabled;
/**
@@ -104,6 +120,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_FULLBACKUP_DEFERRED
*/
+ @GuardedBy("mLock")
private boolean mFullBackupDeferred;
/**
@@ -112,6 +129,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_KEYVALUE_DEFERRED
*/
+ @GuardedBy("mLock")
private boolean mKeyValueBackupDeferred;
/**
@@ -120,6 +138,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_FIREWALL_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mFireWallDisabled;
/**
@@ -128,6 +147,7 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ADJUST_BRIGHTNESS_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mAdjustBrightnessDisabled;
/**
@@ -136,14 +156,22 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_DATASAVER_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mDataSaverDisabled;
/**
+ * {@code true} if launch boost should be disabled on battery saver.
+ */
+ @GuardedBy("mLock")
+ private boolean mLaunchBoostDisabled;
+
+ /**
* This is the flag to decide the gps mode in battery saver mode.
*
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_GPS_MODE
*/
+ @GuardedBy("mLock")
private int mGpsMode;
/**
@@ -153,25 +181,27 @@ public class BatterySaverPolicy extends ContentObserver {
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ADJUST_BRIGHTNESS_FACTOR
*/
+ @GuardedBy("mLock")
private float mAdjustBrightnessFactor;
/**
- * Whether to put all apps in the stand-by mode or not for job scheduler.
+ * Whether to put all apps in the stand-by mode.
*/
- private boolean mForceAllAppsStandbyJobs;
+ @GuardedBy("mLock")
+ private boolean mForceAllAppsStandby;
/**
- * Whether to put all apps in the stand-by mode or not for alarms.
+ * Whether to put all apps in the stand-by mode.
*/
- private boolean mForceAllAppsStandbyAlarms;
+ @GuardedBy("mLock")
+ private boolean mForceBackgroundCheck;
/**
* Weather to show non-essential sensors (e.g. edge sensors) or not.
*/
+ @GuardedBy("mLock")
private boolean mOptionalSensorsDisabled;
- private final Object mLock = new Object();
-
@GuardedBy("mLock")
private Context mContext;
@@ -179,25 +209,25 @@ public class BatterySaverPolicy extends ContentObserver {
private ContentResolver mContentResolver;
@GuardedBy("mLock")
- private final ArrayList<BatterySaverPolicyListener> mListeners = new ArrayList<>();
+ private final List<BatterySaverPolicyListener> mListeners = new ArrayList<>();
/**
* List of [Filename -> content] that should be written when battery saver is activated
- * and the screen is on.
+ * and the device is interactive.
*
* We use this to change the max CPU frequencies.
*/
@GuardedBy("mLock")
- private ArrayMap<String, String> mScreenOnFiles;
+ private ArrayMap<String, String> mFilesForInteractive;
/**
* List of [Filename -> content] that should be written when battery saver is activated
- * and the screen is off.
+ * and the device is non-interactive.
*
* We use this to change the max CPU frequencies.
*/
@GuardedBy("mLock")
- private ArrayMap<String, String> mScreenOffFiles;
+ private ArrayMap<String, String> mFilesForNoninteractive;
public interface BatterySaverPolicyListener {
void onBatterySaverPolicyChanged(BatterySaverPolicy policy);
@@ -228,7 +258,11 @@ public class BatterySaverPolicy extends ContentObserver {
@VisibleForTesting
String getGlobalSetting(String key) {
- return Settings.Global.getString(mContentResolver, key);
+ final ContentResolver cr;
+ synchronized (mLock) {
+ cr = mContentResolver;
+ }
+ return Settings.Global.getString(cr, key);
}
@VisibleForTesting
@@ -236,11 +270,6 @@ public class BatterySaverPolicy extends ContentObserver {
return R.string.config_batterySaverDeviceSpecificConfig;
}
- @VisibleForTesting
- void onChangeForTest() {
- onChange(true, null);
- }
-
@Override
public void onChange(boolean selfChange, Uri uri) {
final BatterySaverPolicyListener[] listeners;
@@ -279,6 +308,11 @@ public class BatterySaverPolicy extends ContentObserver {
mSettings = setting;
mDeviceSpecificSettings = deviceSpecificSetting;
+ if (DEBUG) {
+ Slog.i(TAG, "mSettings=" + mSettings);
+ Slog.i(TAG, "mDeviceSpecificSettings=" + mDeviceSpecificSettings);
+ }
+
final KeyValueListParser parser = new KeyValueListParser(',');
// Non-device-specific parameters.
@@ -297,9 +331,9 @@ public class BatterySaverPolicy extends ContentObserver {
mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
mAdjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true);
- mForceAllAppsStandbyJobs = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_JOBS, true);
- mForceAllAppsStandbyAlarms =
- parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_ALARMS, true);
+ mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true);
+ mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true);
+ mForceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK, true);
mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
// Get default value from Settings.Secure
@@ -315,29 +349,11 @@ public class BatterySaverPolicy extends ContentObserver {
+ deviceSpecificSetting);
}
- mScreenOnFiles = collectParams(parser, KEY_SCREEN_ON_FILE_PREFIX);
- mScreenOffFiles = collectParams(parser, KEY_SCREEN_OFF_FILE_PREFIX);
- }
-
- private static ArrayMap<String, String> collectParams(
- KeyValueListParser parser, String prefix) {
- final ArrayMap<String, String> ret = new ArrayMap<>();
-
- for (int i = parser.size() - 1; i >= 0; i--) {
- final String key = parser.keyAt(i);
- if (!key.startsWith(prefix)) {
- continue;
- }
- final String path = key.substring(prefix.length());
-
- if (!(path.startsWith("/sys/") || path.startsWith("/proc"))) {
- Slog.wtf(TAG, "Invalid path: " + path);
- continue;
- }
+ mFilesForInteractive = (new CpuFrequencies()).parseString(
+ parser.getString(KEY_CPU_FREQ_INTERACTIVE, "")).toSysFileMap();
- ret.put(path, parser.getString(key, ""));
- }
- return ret;
+ mFilesForNoninteractive = (new CpuFrequencies()).parseString(
+ parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, "")).toSysFileMap();
}
/**
@@ -387,11 +403,11 @@ public class BatterySaverPolicy extends ContentObserver {
case ServiceType.VIBRATION:
return builder.setBatterySaverEnabled(mVibrationDisabled)
.build();
- case ServiceType.FORCE_ALL_APPS_STANDBY_JOBS:
- return builder.setBatterySaverEnabled(mForceAllAppsStandbyJobs)
+ case ServiceType.FORCE_ALL_APPS_STANDBY:
+ return builder.setBatterySaverEnabled(mForceAllAppsStandby)
.build();
- case ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS:
- return builder.setBatterySaverEnabled(mForceAllAppsStandbyAlarms)
+ case ServiceType.FORCE_BACKGROUND_CHECK:
+ return builder.setBatterySaverEnabled(mForceBackgroundCheck)
.build();
case ServiceType.OPTIONAL_SENSORS:
return builder.setBatterySaverEnabled(mOptionalSensorsDisabled)
@@ -403,9 +419,15 @@ public class BatterySaverPolicy extends ContentObserver {
}
}
- public ArrayMap<String, String> getFileValues(boolean screenOn) {
+ public ArrayMap<String, String> getFileValues(boolean interactive) {
+ synchronized (mLock) {
+ return interactive ? mFilesForInteractive : mFilesForNoninteractive;
+ }
+ }
+
+ public boolean isLaunchBoostDisabled() {
synchronized (mLock) {
- return screenOn ? mScreenOnFiles : mScreenOffFiles;
+ return mLaunchBoostDisabled;
}
}
@@ -413,10 +435,10 @@ public class BatterySaverPolicy extends ContentObserver {
synchronized (mLock) {
pw.println();
pw.println("Battery saver policy");
- pw.println(" Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS);
- pw.println(" value: " + mSettings);
- pw.println(" Settings " + mDeviceSpecificSettingsSource);
- pw.println(" value: " + mDeviceSpecificSettings);
+ pw.println(" Settings: " + Settings.Global.BATTERY_SAVER_CONSTANTS);
+ pw.println(" value: " + mSettings);
+ pw.println(" Settings: " + mDeviceSpecificSettingsSource);
+ pw.println(" value: " + mDeviceSpecificSettings);
pw.println();
pw.println(" " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
@@ -425,20 +447,21 @@ public class BatterySaverPolicy extends ContentObserver {
pw.println(" " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
pw.println(" " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
pw.println(" " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
+ pw.println(" " + KEY_LAUNCH_BOOST_DISABLED + "=" + mLaunchBoostDisabled);
pw.println(" " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode);
- pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY_JOBS + "=" + mForceAllAppsStandbyJobs);
- pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY_ALARMS + "=" + mForceAllAppsStandbyAlarms);
+ pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY + "=" + mForceAllAppsStandby);
+ pw.println(" " + KEY_FORCE_BACKGROUND_CHECK + "=" + mForceBackgroundCheck);
pw.println(" " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
pw.println();
- pw.print(" Screen On Files:\n");
- dumpMap(pw, " ", mScreenOnFiles);
+ pw.print(" Interactive File values:\n");
+ dumpMap(pw, " ", mFilesForInteractive);
pw.println();
- pw.print(" Screen Off Files:\n");
- dumpMap(pw, " ", mScreenOffFiles);
+ pw.print(" Noninteractive File values:\n");
+ dumpMap(pw, " ", mFilesForNoninteractive);
pw.println();
}
}
diff --git a/com/android/server/power/Notifier.java b/com/android/server/power/Notifier.java
index 0ecf0e1e..8ee26f29 100644
--- a/com/android/server/power/Notifier.java
+++ b/com/android/server/power/Notifier.java
@@ -25,6 +25,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -49,7 +50,6 @@ import android.os.WorkSource;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Slog;
-import android.view.WindowManagerPolicy;
import android.view.inputmethod.InputMethodManagerInternal;
/**
diff --git a/com/android/server/power/PowerManagerService.java b/com/android/server/power/PowerManagerService.java
index a47b8095..7f1a534c 100644
--- a/com/android/server/power/PowerManagerService.java
+++ b/com/android/server/power/PowerManagerService.java
@@ -69,7 +69,6 @@ import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
-import android.view.WindowManagerPolicy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsService;
@@ -90,6 +89,7 @@ import com.android.server.Watchdog;
import com.android.server.am.BatteryStatsService;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
+import com.android.server.policy.WindowManagerPolicy;
import com.android.server.power.batterysaver.BatterySaverController;
import libcore.util.Objects;
@@ -1457,6 +1457,10 @@ public final class PowerManagerService extends SystemService
case PowerManager.GO_TO_SLEEP_REASON_HDMI:
Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")...");
break;
+ case PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY:
+ Slog.i(TAG, "Going to sleep by an accessibility service request (uid "
+ + uid +")...");
+ break;
default:
Slog.i(TAG, "Going to sleep by application request (uid " + uid +")...");
reason = PowerManager.GO_TO_SLEEP_REASON_APPLICATION;
@@ -3105,7 +3109,16 @@ public final class PowerManagerService extends SystemService
mIsVrModeEnabled = enabled;
}
- public static void powerHintInternal(int hintId, int data) {
+ private void powerHintInternal(int hintId, int data) {
+ // Maybe filter the event.
+ switch (hintId) {
+ case PowerHint.LAUNCH: // 1: activate launch boost 0: deactivate.
+ if (data == 1 && mBatterySaverController.isLaunchBoostDisabled()) {
+ return;
+ }
+ break;
+ }
+
nativeSendPowerHint(hintId, data);
}
diff --git a/com/android/server/power/ShutdownThread.java b/com/android/server/power/ShutdownThread.java
index 6bf725e1..6fb345bc 100644
--- a/com/android/server/power/ShutdownThread.java
+++ b/com/android/server/power/ShutdownThread.java
@@ -20,10 +20,7 @@ package com.android.server.power;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.IActivityManager;
-import android.app.KeyguardManager;
import android.app.ProgressDialog;
-import android.app.WallpaperColors;
-import android.app.WallpaperManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.IBluetoothManager;
import android.content.BroadcastReceiver;
@@ -31,8 +28,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
import android.media.AudioAttributes;
import android.os.FileUtils;
import android.os.Handler;
@@ -47,8 +42,6 @@ import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
-import android.os.storage.IStorageManager;
-import android.os.storage.IStorageShutdownObserver;
import android.util.ArrayMap;
import android.util.Log;
import android.util.TimingsTraceLog;
@@ -123,7 +116,6 @@ public final class ShutdownThread extends Thread {
private static String METRIC_RADIOS = "shutdown_radios";
private static String METRIC_BT = "shutdown_bt";
private static String METRIC_RADIO = "shutdown_radio";
- private static String METRIC_SM = "shutdown_storage_manager";
private final Object mActionDoneSync = new Object();
private boolean mActionDone;
@@ -526,54 +518,6 @@ public final class ShutdownThread extends Thread {
shutdownTimingLog.traceEnd(); // ShutdownRadios
metricEnded(METRIC_RADIOS);
- // Shutdown StorageManagerService to ensure media is in a safe state
- IStorageShutdownObserver observer = new IStorageShutdownObserver.Stub() {
- public void onShutDownComplete(int statusCode) throws RemoteException {
- Log.w(TAG, "Result code " + statusCode + " from StorageManagerService.shutdown");
- actionDone();
- }
- };
-
- Log.i(TAG, "Shutting down StorageManagerService");
- shutdownTimingLog.traceBegin("ShutdownStorageManager");
- metricStarted(METRIC_SM);
-
- // Set initial variables and time out time.
- mActionDone = false;
- final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
- synchronized (mActionDoneSync) {
- try {
- final IStorageManager storageManager = IStorageManager.Stub.asInterface(
- ServiceManager.checkService("mount"));
- if (storageManager != null) {
- storageManager.shutdown(observer);
- } else {
- Log.w(TAG, "StorageManagerService unavailable for shutdown");
- }
- } catch (Exception e) {
- Log.e(TAG, "Exception during StorageManagerService shutdown", e);
- }
- while (!mActionDone) {
- long delay = endShutTime - SystemClock.elapsedRealtime();
- if (delay <= 0) {
- Log.w(TAG, "StorageManager shutdown wait timed out");
- break;
- } else if (mRebootHasProgressBar) {
- int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 *
- (MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) /
- MAX_SHUTDOWN_WAIT_TIME);
- status += RADIO_STOP_PERCENT;
- sInstance.setRebootProgress(status, null);
- }
- try {
- mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
- } catch (InterruptedException e) {
- }
- }
- }
- shutdownTimingLog.traceEnd(); // ShutdownStorageManager
- metricEnded(METRIC_SM);
-
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
@@ -585,6 +529,7 @@ public final class ShutdownThread extends Thread {
shutdownTimingLog.traceEnd(); // SystemServerShutdown
metricEnded(METRIC_SYSTEM_SERVER);
saveMetrics(mReboot);
+ // Remaining work will be done by init, including vold shutdown
rebootOrShutdown(mContext, mReboot, mReason);
}
diff --git a/com/android/server/power/batterysaver/BatterySaverController.java b/com/android/server/power/batterysaver/BatterySaverController.java
index b3e85383..80bc9359 100644
--- a/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/com/android/server/power/batterysaver/BatterySaverController.java
@@ -16,6 +16,7 @@
package com.android.server.power.batterysaver;
import android.Manifest;
+import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -25,6 +26,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
+import android.os.PowerManagerInternal;
import android.os.PowerManagerInternal.LowPowerModeListener;
import android.os.PowerSaveState;
import android.os.UserHandle;
@@ -33,7 +35,9 @@ import android.util.Slog;
import android.widget.Toast;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
import com.android.server.power.BatterySaverPolicy;
import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
import com.android.server.power.PowerManagerService;
@@ -46,7 +50,7 @@ import java.util.ArrayList;
public class BatterySaverController implements BatterySaverPolicyListener {
static final String TAG = "BatterySaverController";
- static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE
+ static final boolean DEBUG = BatterySaverPolicy.DEBUG;
private final Object mLock = new Object();
private final Context mContext;
@@ -63,20 +67,17 @@ public class BatterySaverController implements BatterySaverPolicyListener {
@GuardedBy("mLock")
private boolean mEnabled;
- /**
- * Keep track of the previous enabled state, which we use to decide when to send broadcasts,
- * which we don't want to send only when the screen state changes.
- */
- @GuardedBy("mLock")
- private boolean mWasEnabled;
-
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case Intent.ACTION_SCREEN_ON:
case Intent.ACTION_SCREEN_OFF:
- mHandler.postStateChanged();
+ if (!isEnabled()) {
+ return; // No need to send it if not enabled.
+ }
+ // Don't send the broadcast, because we never did so in this case.
+ mHandler.postStateChanged(/*sendBroadcast=*/ false);
break;
}
}
@@ -109,6 +110,9 @@ public class BatterySaverController implements BatterySaverPolicyListener {
final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mReceiver, filter);
+
+ mFileUpdater.systemReady(LocalServices.getService(ActivityManagerInternal.class)
+ .isRuntimeRestarted());
}
private PowerManager getPowerManager() {
@@ -121,25 +125,32 @@ public class BatterySaverController implements BatterySaverPolicyListener {
@Override
public void onBatterySaverPolicyChanged(BatterySaverPolicy policy) {
- mHandler.postStateChanged();
+ if (!isEnabled()) {
+ return; // No need to send it if not enabled.
+ }
+ mHandler.postStateChanged(/*sendBroadcast=*/ true);
}
private class MyHandler extends Handler {
- private final int MSG_STATE_CHANGED = 1;
+ private static final int MSG_STATE_CHANGED = 1;
+
+ private static final int ARG_DONT_SEND_BROADCAST = 0;
+ private static final int ARG_SEND_BROADCAST = 1;
public MyHandler(Looper looper) {
super(looper);
}
- public void postStateChanged() {
- obtainMessage(MSG_STATE_CHANGED).sendToTarget();
+ public void postStateChanged(boolean sendBroadcast) {
+ obtainMessage(MSG_STATE_CHANGED, sendBroadcast ?
+ ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, 0).sendToTarget();
}
@Override
public void dispatchMessage(Message msg) {
switch (msg.what) {
case MSG_STATE_CHANGED:
- handleBatterySaverStateChanged();
+ handleBatterySaverStateChanged(msg.arg1 == ARG_SEND_BROADCAST);
break;
}
}
@@ -155,53 +166,75 @@ public class BatterySaverController implements BatterySaverPolicyListener {
}
mEnabled = enable;
- mHandler.postStateChanged();
+ mHandler.postStateChanged(/*sendBroadcast=*/ true);
+ }
+ }
+
+ /** @return whether battery saver is enabled or not. */
+ boolean isEnabled() {
+ synchronized (mLock) {
+ return mEnabled;
}
}
/**
+ * @return true if launch boost should currently be disabled.
+ */
+ public boolean isLaunchBoostDisabled() {
+ return isEnabled() && mBatterySaverPolicy.isLaunchBoostDisabled();
+ }
+
+ /**
* Dispatch power save events to the listeners.
*
- * This is always called on the handler thread.
+ * This method is always called on the handler thread.
+ *
+ * This method is called only in the following cases:
+ * - When battery saver becomes activated.
+ * - When battery saver becomes deactivated.
+ * - When battery saver is on the interactive state changes.
+ * - When battery saver is on the battery saver policy changes.
*/
- void handleBatterySaverStateChanged() {
+ void handleBatterySaverStateChanged(boolean sendBroadcast) {
final LowPowerModeListener[] listeners;
- final boolean wasEnabled;
final boolean enabled;
- final boolean isScreenOn = getPowerManager().isInteractive();
+ final boolean isInteractive = getPowerManager().isInteractive();
final ArrayMap<String, String> fileValues;
synchronized (mLock) {
- Slog.i(TAG, "Battery saver enabled: screen on=" + isScreenOn);
+ Slog.i(TAG, "Battery saver " + (mEnabled ? "enabled" : "disabled")
+ + ": isInteractive=" + isInteractive);
listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
- wasEnabled = mWasEnabled;
enabled = mEnabled;
if (enabled) {
- fileValues = mBatterySaverPolicy.getFileValues(isScreenOn);
+ fileValues = mBatterySaverPolicy.getFileValues(isInteractive);
} else {
fileValues = null;
}
}
- PowerManagerService.powerHintInternal(PowerHint.LOW_POWER, enabled ? 1 : 0);
-
- if (enabled) {
- // STOPSHIP Remove the toast.
- Toast.makeText(mContext,
- com.android.internal.R.string.battery_saver_warning,
- Toast.LENGTH_LONG).show();
+ final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+ if (pmi != null) {
+ pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0);
}
- if (fileValues == null || fileValues.size() == 0) {
+ if (ArrayUtils.isEmpty(fileValues)) {
mFileUpdater.restoreDefault();
} else {
mFileUpdater.writeFiles(fileValues);
}
- if (enabled != wasEnabled) {
+ if (sendBroadcast) {
+ if (enabled) {
+ // STOPSHIP Remove the toast.
+ Toast.makeText(mContext,
+ com.android.internal.R.string.battery_saver_warning,
+ Toast.LENGTH_LONG).show();
+ }
+
if (DEBUG) {
Slog.i(TAG, "Sending broadcasts for mode: " + enabled);
}
@@ -231,9 +264,5 @@ public class BatterySaverController implements BatterySaverPolicyListener {
listener.onLowPowerModeChanged(result);
}
}
-
- synchronized (mLock) {
- mWasEnabled = enabled;
- }
}
}
diff --git a/com/android/server/power/batterysaver/CpuFrequencies.java b/com/android/server/power/batterysaver/CpuFrequencies.java
new file mode 100644
index 00000000..1629486b
--- /dev/null
+++ b/com/android/server/power/batterysaver/CpuFrequencies.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.power.batterysaver;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Map;
+
+
+/**
+ * Helper to parse a list of "core-number:frequency" pairs concatenated with / as a separator,
+ * and convert them into a map of "filename -> value" that should be written to
+ * /sys/.../scaling_max_freq.
+ *
+ * Example input: "0:1900800/4:2500000", which will be converted into:
+ * "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq" "1900800"
+ * "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq" "2500000"
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
+ */
+public class CpuFrequencies {
+ private static final String TAG = "CpuFrequencies";
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<Integer, Long> mCoreAndFrequencies = new ArrayMap<>();
+
+ public CpuFrequencies() {
+ }
+
+ /**
+ * Parse a string.
+ */
+ public CpuFrequencies parseString(String cpuNumberAndFrequencies) {
+ synchronized (mLock) {
+ mCoreAndFrequencies.clear();
+ try {
+ for (String pair : cpuNumberAndFrequencies.split("/")) {
+ final String[] coreAndFreq = pair.split(":", 2);
+
+ if (coreAndFreq.length != 2) {
+ throw new IllegalArgumentException("Wrong format");
+ }
+ final int core = Integer.parseInt(coreAndFreq[0]);
+ final long freq = Long.parseLong(coreAndFreq[1]);
+
+ mCoreAndFrequencies.put(core, freq);
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Invalid configuration: " + cpuNumberAndFrequencies, e);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Return a new map containing the filename-value pairs.
+ */
+ public ArrayMap<String, String> toSysFileMap() {
+ final ArrayMap<String, String> map = new ArrayMap<>();
+ addToSysFileMap(map);
+ return map;
+ }
+
+ /**
+ * Add the filename-value pairs to an existing map.
+ */
+ public void addToSysFileMap(Map<String, String> map) {
+ synchronized (mLock) {
+ final int size = mCoreAndFrequencies.size();
+
+ for (int i = 0; i < size; i++) {
+ final int core = mCoreAndFrequencies.keyAt(i);
+ final long freq = mCoreAndFrequencies.valueAt(i);
+
+ final String file = "/sys/devices/system/cpu/cpu" + Integer.toString(core) +
+ "/cpufreq/scaling_max_freq";
+
+ map.put(file, Long.toString(freq));
+ }
+ }
+ }
+}
diff --git a/com/android/server/power/batterysaver/FileUpdater.java b/com/android/server/power/batterysaver/FileUpdater.java
index cfe8fc49..e0ab9e93 100644
--- a/com/android/server/power/batterysaver/FileUpdater.java
+++ b/com/android/server/power/batterysaver/FileUpdater.java
@@ -16,40 +16,389 @@
package com.android.server.power.batterysaver;
import android.content.Context;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemProperties;
import android.util.ArrayMap;
+import android.util.AtomicFile;
import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+import com.android.server.IoThread;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Map;
/**
* Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files
- * with retry and to restore the original values.
+ * with retries. It also support restoring to the file original values.
*
- * TODO Implement it
+ * Retries are needed because writing to "/sys/.../scaling_max_freq" returns EIO when the current
+ * frequency happens to be above the new max frequency.
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
*/
public class FileUpdater {
private static final String TAG = BatterySaverController.TAG;
private static final boolean DEBUG = BatterySaverController.DEBUG;
+ /**
+ * If this system property is set to 1, it'll skip all file writes. This can be used when
+ * one needs to change max CPU frequency for benchmarking, for example.
+ */
+ private static final String PROP_SKIP_WRITE = "debug.batterysaver.no_write_files";
+
+ private static final String TAG_DEFAULT_ROOT = "defaults";
+
+ // Don't do disk access with this lock held.
private final Object mLock = new Object();
+
private final Context mContext;
+ private final Handler mHandler;
+
+ /**
+ * Filename -> value map that holds pending writes.
+ */
+ @GuardedBy("mLock")
+ private final ArrayMap<String, String> mPendingWrites = new ArrayMap<>();
+
+ /**
+ * Filename -> value that holds the original value of each file.
+ */
+ @GuardedBy("mLock")
+ private final ArrayMap<String, String> mDefaultValues = new ArrayMap<>();
+
+ /** Number of retries. We give up on writing after {@link #MAX_RETRIES} retries. */
+ @GuardedBy("mLock")
+ private int mRetries = 0;
+
+ private final int MAX_RETRIES;
+
+ private final long RETRY_INTERVAL_MS;
+
+ /**
+ * "Official" constructor. Don't use the other constructor in the production code.
+ */
public FileUpdater(Context context) {
+ this(context, IoThread.get().getLooper(), 10, 5000);
+ }
+
+ /**
+ * Constructor for test.
+ */
+ @VisibleForTesting
+ FileUpdater(Context context, Looper looper, int maxRetries, int retryIntervalMs) {
mContext = context;
+ mHandler = new Handler(looper);
+
+ MAX_RETRIES = maxRetries;
+ RETRY_INTERVAL_MS = retryIntervalMs;
+ }
+
+ public void systemReady(boolean runtimeRestarted) {
+ synchronized (mLock) {
+ if (runtimeRestarted) {
+ // If it runtime restarted, read the original values from the disk and apply.
+ if (loadDefaultValuesLocked()) {
+ Slog.d(TAG, "Default values loaded after runtime restart; writing them...");
+ restoreDefault();
+ }
+ } else {
+ // Delete it, without checking the result. (file-not-exist is not an exception.)
+ injectDefaultValuesFilename().delete();
+ }
+ }
}
+ /**
+ * Write values to files. (Note the actual writes happen ASAP but asynchronously.)
+ */
public void writeFiles(ArrayMap<String, String> fileValues) {
- if (DEBUG) {
- final int size = fileValues.size();
- for (int i = 0; i < size; i++) {
- Slog.d(TAG, "Writing '" + fileValues.valueAt(i)
- + "' to '" + fileValues.keyAt(i) + "'");
+ synchronized (mLock) {
+ for (int i = fileValues.size() - 1; i >= 0; i--) {
+ final String file = fileValues.keyAt(i);
+ final String value = fileValues.valueAt(i);
+
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduling write: '" + value + "' to '" + file + "'");
+ }
+
+ mPendingWrites.put(file, value);
+
}
+ mRetries = 0;
+
+ mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable);
+ mHandler.post(mHandleWriteOnHandlerRunnable);
}
}
+ /**
+ * Restore the default values.
+ */
public void restoreDefault() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Resetting file default values.");
+ }
+ mPendingWrites.clear();
+
+ writeFiles(mDefaultValues);
+ }
+ }
+
+ private Runnable mHandleWriteOnHandlerRunnable = () -> handleWriteOnHandler();
+
+ /** Convert map keys into a single string for debug messages. */
+ private String getKeysString(Map<String, String> source) {
+ return new ArrayList<>(source.keySet()).toString();
+ }
+
+ /** Clone an ArrayMap. */
+ private ArrayMap<String, String> cloneMap(ArrayMap<String, String> source) {
+ return new ArrayMap<>(source);
+ }
+
+ /**
+ * Called on the handler and writes {@link #mPendingWrites} to the disk.
+ *
+ * When it about to write to each file for the first time, it'll read the file and store
+ * the original value in {@link #mDefaultValues}.
+ */
+ private void handleWriteOnHandler() {
+ // We don't want to access the disk with the lock held, so copy the pending writes to
+ // a local map.
+ final ArrayMap<String, String> writes;
+ synchronized (mLock) {
+ if (mPendingWrites.size() == 0) {
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Writing files: (# retries=" + mRetries + ") " +
+ getKeysString(mPendingWrites));
+ }
+
+ writes = cloneMap(mPendingWrites);
+ }
+
+ // Then write.
+
+ boolean needRetry = false;
+
+ final int size = writes.size();
+ for (int i = 0; i < size; i++) {
+ final String file = writes.keyAt(i);
+ final String value = writes.valueAt(i);
+
+ // Make sure the default value is loaded.
+ if (!ensureDefaultLoaded(file)) {
+ continue;
+ }
+
+ // Write to the file. When succeeded, remove it from the pending list.
+ // Otherwise, schedule a retry.
+ try {
+ injectWriteToFile(file, value);
+
+ removePendingWrite(file);
+ } catch (IOException e) {
+ needRetry = true;
+ }
+ }
+ if (needRetry) {
+ scheduleRetry();
+ }
+ }
+
+ private void removePendingWrite(String file) {
+ synchronized (mLock) {
+ mPendingWrites.remove(file);
+ }
+ }
+
+ private void scheduleRetry() {
+ synchronized (mLock) {
+ if (mPendingWrites.size() == 0) {
+ return; // Shouldn't happen but just in case.
+ }
+
+ mRetries++;
+ if (mRetries > MAX_RETRIES) {
+ doWtf("Gave up writing files: " + getKeysString(mPendingWrites));
+ return;
+ }
+
+ mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable);
+ mHandler.postDelayed(mHandleWriteOnHandlerRunnable, RETRY_INTERVAL_MS);
+ }
+ }
+
+ /**
+ * Make sure {@link #mDefaultValues} has the default value loaded for {@code file}.
+ *
+ * @return true if the default value is loaded. false if the file cannot be read.
+ */
+ private boolean ensureDefaultLoaded(String file) {
+ // Has the default already?
+ synchronized (mLock) {
+ if (mDefaultValues.containsKey(file)) {
+ return true;
+ }
+ }
+ final String originalValue;
+ try {
+ originalValue = injectReadFromFileTrimmed(file);
+ } catch (IOException e) {
+ // If the file is not readable, assume can't write too.
+ injectWtf("Unable to read from file", e);
+
+ removePendingWrite(file);
+ return false;
+ }
+ synchronized (mLock) {
+ mDefaultValues.put(file, originalValue);
+ saveDefaultValuesLocked();
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ String injectReadFromFileTrimmed(String file) throws IOException {
+ return IoUtils.readFileAsString(file).trim();
+ }
+
+ @VisibleForTesting
+ void injectWriteToFile(String file, String value) throws IOException {
+ if (injectShouldSkipWrite()) {
+ Slog.i(TAG, "Skipped writing to '" + file + "'");
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'");
+ }
+ try (FileWriter out = new FileWriter(file)) {
+ out.write(value);
+ } catch (IOException | RuntimeException e) {
+ Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage());
+ throw e;
+ }
+ }
+
+ private void saveDefaultValuesLocked() {
+ final AtomicFile file = new AtomicFile(injectDefaultValuesFilename());
+
+ FileOutputStream outs = null;
+ try {
+ file.getBaseFile().getParentFile().mkdirs();
+ outs = file.startWrite();
+
+ // Write to XML
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(outs, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, TAG_DEFAULT_ROOT);
+
+ XmlUtils.writeMapXml(mDefaultValues, out, null);
+
+ // Epilogue.
+ out.endTag(null, TAG_DEFAULT_ROOT);
+ out.endDocument();
+
+ // Close.
+ file.finishWrite(outs);
+ } catch (IOException | XmlPullParserException | RuntimeException e) {
+ Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+ file.failWrite(outs);
+ }
+ }
+
+ @VisibleForTesting
+ boolean loadDefaultValuesLocked() {
+ final AtomicFile file = new AtomicFile(injectDefaultValuesFilename());
if (DEBUG) {
- Slog.d(TAG, "Resetting file default values");
+ Slog.d(TAG, "Loading from " + file.getBaseFile());
}
+ Map<String, String> read = null;
+ try (FileInputStream in = file.openRead()) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, StandardCharsets.UTF_8.name());
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+ // Check the root tag
+ final String tag = parser.getName();
+ if (depth == 1) {
+ if (!TAG_DEFAULT_ROOT.equals(tag)) {
+ Slog.e(TAG, "Invalid root tag: " + tag);
+ return false;
+ }
+ continue;
+ }
+ final String[] tagName = new String[1];
+ read = (ArrayMap<String, String>) XmlUtils.readThisArrayMapXml(parser,
+ TAG_DEFAULT_ROOT, tagName, null);
+ }
+ } catch (FileNotFoundException e) {
+ read = null;
+ } catch (IOException | XmlPullParserException | RuntimeException e) {
+ Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
+ }
+ if (read != null) {
+ mDefaultValues.clear();
+ mDefaultValues.putAll(read);
+ return true;
+ }
+ return false;
+ }
+
+ private void doWtf(String message) {
+ injectWtf(message, null);
+ }
+
+ @VisibleForTesting
+ void injectWtf(String message, Throwable e) {
+ Slog.wtf(TAG, message, e);
+ }
+
+ File injectDefaultValuesFilename() {
+ final File dir = new File(Environment.getDataSystemDirectory(), "battery-saver");
+ dir.mkdirs();
+ return new File(dir, "default-values.xml");
+ }
+
+ @VisibleForTesting
+ boolean injectShouldSkipWrite() {
+ return SystemProperties.getBoolean(PROP_SKIP_WRITE, false);
+ }
+
+ @VisibleForTesting
+ ArrayMap<String, String> getDefaultValuesForTest() {
+ return mDefaultValues;
}
}
diff --git a/com/android/server/stats/StatsCompanionService.java b/com/android/server/stats/StatsCompanionService.java
index dafab770..e240ec5e 100644
--- a/com/android/server/stats/StatsCompanionService.java
+++ b/com/android/server/stats/StatsCompanionService.java
@@ -75,6 +75,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
private final PendingIntent mPullingAlarmIntent;
private final BroadcastReceiver mAppUpdateReceiver;
private final BroadcastReceiver mUserUpdateReceiver;
+ private final ShutdownEventReceiver mShutdownEventReceiver;
private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
private final KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
@@ -109,6 +110,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
}
}
};
+ mShutdownEventReceiver = new ShutdownEventReceiver();
Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED.");
PowerProfile powerProfile = new PowerProfile(context);
final int numClusters = powerProfile.getNumCpuClusters();
@@ -239,20 +241,45 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
Slog.d(TAG, "Time to poll something.");
synchronized (sStatsdLock) {
if (sStatsd == null) {
- Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing");
+ Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing.");
return;
}
try {
// Two-way call to statsd to retain AlarmManager wakelock
sStatsd.informPollAlarmFired();
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to inform statsd of pulling alarm firing", e);
+ Slog.w(TAG, "Failed to inform statsd of pulling alarm firing.", e);
}
}
// AlarmManager releases its own wakelock here.
}
}
+ public final static class ShutdownEventReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ /**
+ * Skip immediately if intent is not relevant to device shutdown.
+ */
+ if (!intent.getAction().equals(Intent.ACTION_REBOOT)
+ && !intent.getAction().equals(Intent.ACTION_SHUTDOWN)) {
+ return;
+ }
+ Slog.i(TAG, "StatsCompanionService noticed a shutdown.");
+ synchronized (sStatsdLock) {
+ if (sStatsd == null) {
+ Slog.w(TAG, "Could not access statsd to inform it of a shutdown event.");
+ return;
+ }
+ try {
+ sStatsd.writeDataToDisk();
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to inform statsd of a shutdown event.", e);
+ }
+ }
+ }
+ }
+
@Override // Binder call
public void setAnomalyAlarm(long timestampMs) {
enforceCallingPermission();
@@ -496,6 +523,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
sayHiToStatsd(); // tell statsd that we're ready too and link to it
}
+ @Override
+ public void triggerUidSnapshot() {
+ enforceCallingPermission();
+ synchronized (sStatsdLock) {
+ try {
+ informAllUidsLocked(mContext);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to trigger uid snapshot.", e);
+ }
+ }
+ }
+
private void enforceCallingPermission() {
if (Binder.getCallingPid() == Process.myPid()) {
return;
@@ -572,7 +611,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
forgetEverything();
}
- // Setup broadcast receiver for updates
+ // 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);
@@ -587,6 +626,12 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
mContext.registerReceiverAsUser(mUserUpdateReceiver, UserHandle.ALL,
filter, null, null);
+ // Setup receiver for device reboots or shutdowns.
+ filter = new IntentFilter(Intent.ACTION_REBOOT);
+ filter.addAction(Intent.ACTION_SHUTDOWN);
+ mContext.registerReceiverAsUser(
+ mShutdownEventReceiver, UserHandle.ALL, filter, null, null);
+
// Pull the latest state of UID->app name, version mapping when statsd starts.
informAllUidsLocked(mContext);
} catch (RemoteException e) {
@@ -609,6 +654,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
sStatsd = null;
mContext.unregisterReceiver(mAppUpdateReceiver);
mContext.unregisterReceiver(mUserUpdateReceiver);
+ mContext.unregisterReceiver(mShutdownEventReceiver);
cancelAnomalyAlarm();
cancelPullingAlarms();
}
diff --git a/com/android/server/statusbar/StatusBarManagerInternal.java b/com/android/server/statusbar/StatusBarManagerInternal.java
index b07fe98d..3792bc67 100644
--- a/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -90,6 +90,13 @@ public interface StatusBarManagerInternal {
boolean showShutdownUi(boolean isReboot, String requestString);
+ /**
+ * Show a rotation suggestion that a user may approve to rotate the screen.
+ *
+ * @param rotation rotation suggestion
+ */
+ void onProposedRotationChanged(int rotation);
+
public interface GlobalActionsListener {
/**
* Called when sysui starts and connects its status bar, or when the status bar binder
diff --git a/com/android/server/statusbar/StatusBarManagerService.java b/com/android/server/statusbar/StatusBarManagerService.java
index c78a3406..c7c03b48 100644
--- a/com/android/server/statusbar/StatusBarManagerService.java
+++ b/com/android/server/statusbar/StatusBarManagerService.java
@@ -406,6 +406,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
}
return false;
}
+
+ @Override
+ public void onProposedRotationChanged(int rotation) {
+ if (mBar != null){
+ try {
+ mBar.onProposedRotationChanged(rotation);
+ } catch (RemoteException ex) {}
+ }
+ }
};
// ================================================================================
diff --git a/com/android/server/usage/AppIdleHistory.java b/com/android/server/usage/AppIdleHistory.java
index c5ca3304..ee112415 100644
--- a/com/android/server/usage/AppIdleHistory.java
+++ b/com/android/server/usage/AppIdleHistory.java
@@ -91,8 +91,6 @@ public class AppIdleHistory {
private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration
private long mScreenOnDuration; // Total screen on duration since device was "born"
- private long mElapsedTimeThreshold;
- private long mScreenOnTimeThreshold;
private final File mStorageDir;
private boolean mScreenOn;
@@ -113,11 +111,6 @@ public class AppIdleHistory {
readScreenOnTime();
}
- public void setThresholds(long elapsedTimeThreshold, long screenOnTimeThreshold) {
- mElapsedTimeThreshold = elapsedTimeThreshold;
- mScreenOnTimeThreshold = screenOnTimeThreshold;
- }
-
public void updateDisplay(boolean screenOn, long elapsedRealtime) {
if (screenOn == mScreenOn) return;
@@ -186,7 +179,7 @@ public class AppIdleHistory {
writeScreenOnTime();
}
- public void reportUsage(String packageName, int userId, long elapsedRealtime) {
+ public int reportUsage(String packageName, int userId, long elapsedRealtime) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
elapsedRealtime, true);
@@ -197,12 +190,33 @@ public class AppIdleHistory {
+ (elapsedRealtime - mElapsedSnapshot);
appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
- appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_ACTIVE;
+ if (appUsageHistory.currentBucket > STANDBY_BUCKET_ACTIVE) {
+ appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
+ if (DEBUG) {
+ Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+ + ", reason=" + appUsageHistory.bucketingReason);
+ }
+ }
appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
- if (DEBUG) {
- Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
- + ", reason=" + appUsageHistory.bucketingReason);
+
+ return appUsageHistory.currentBucket;
+ }
+
+ public int reportMildUsage(String packageName, int userId, long elapsedRealtime) {
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+ elapsedRealtime, true);
+ if (appUsageHistory.currentBucket > STANDBY_BUCKET_WORKING_SET) {
+ appUsageHistory.currentBucket = STANDBY_BUCKET_WORKING_SET;
+ if (DEBUG) {
+ Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+ + ", reason=" + appUsageHistory.bucketingReason);
+ }
}
+ // TODO: Should this be a different reason for partial usage?
+ appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
+
+ return appUsageHistory.currentBucket;
}
public void setIdle(String packageName, int userId, long elapsedRealtime) {
@@ -313,7 +327,8 @@ public class AppIdleHistory {
return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
}
- public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
+ /* Returns the new standby bucket the app is assigned to */
+ public int setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
elapsedRealtime, true);
@@ -325,6 +340,7 @@ public class AppIdleHistory {
// This is to pretend that the app was just used, don't freeze the state anymore.
appUsageHistory.bucketingReason = REASON_USAGE;
}
+ return appUsageHistory.currentBucket;
}
public void clearUsage(String packageName, int userId) {
diff --git a/com/android/server/usage/AppStandbyController.java b/com/android/server/usage/AppStandbyController.java
index 5623a68f..3c099c24 100644
--- a/com/android/server/usage/AppStandbyController.java
+++ b/com/android/server/usage/AppStandbyController.java
@@ -91,14 +91,14 @@ public class AppStandbyController {
0,
0,
COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR,
- COMPRESS_TIME ? 240 * 1000 : 8 * ONE_HOUR
+ COMPRESS_TIME ? 240 * 1000 : 2 * ONE_HOUR
};
static final long[] ELAPSED_TIME_THRESHOLDS = {
0,
COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR,
- COMPRESS_TIME ? 4 * ONE_MINUTE : 2 * ONE_DAY,
- COMPRESS_TIME ? 16 * ONE_MINUTE : 8 * ONE_DAY
+ COMPRESS_TIME ? 4 * ONE_MINUTE : 24 * ONE_HOUR,
+ COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR
};
static final int[] THRESHOLD_BUCKETS = {
@@ -140,9 +140,7 @@ public class AppStandbyController {
static final int MSG_PAROLE_STATE_CHANGED = 9;
static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
- long mAppIdleScreenThresholdMillis;
long mCheckIdleIntervalMillis;
- long mAppIdleWallclockThresholdMillis;
long mAppIdleParoleIntervalMillis;
long mAppIdleParoleDurationMillis;
long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
@@ -156,7 +154,7 @@ public class AppStandbyController {
private volatile boolean mPendingOneTimeCheckIdleStates;
- private final Handler mHandler;
+ private final AppStandbyHandler mHandler;
private final Context mContext;
// TODO: Provide a mechanism to set an external bucketing service
@@ -229,6 +227,7 @@ public class AppStandbyController {
// Get sync adapters for the authority
String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
authority, userId);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
for (String packageName: packages) {
// Only force the sync adapters to active if the provider is not in the same package and
// the sync adapter is a system package.
@@ -239,7 +238,12 @@ public class AppStandbyController {
continue;
}
if (!packageName.equals(providerPkgName)) {
- setAppIdleAsync(packageName, false, userId);
+ synchronized (mAppIdleLock) {
+ int newBucket = mAppIdleHistory.reportMildUsage(packageName, userId,
+ elapsedRealtime);
+ maybeInformListeners(packageName, userId, elapsedRealtime,
+ newBucket);
+ }
}
} catch (PackageManager.NameNotFoundException e) {
// Shouldn't happen
@@ -408,6 +412,7 @@ public class AppStandbyController {
private void maybeInformListeners(String packageName, int userId,
long elapsedRealtime, int bucket) {
synchronized (mAppIdleLock) {
+ // TODO: fold these into one call + lookup for efficiency if needed
if (mAppIdleHistory.shouldInformListeners(packageName, userId,
elapsedRealtime, bucket)) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
@@ -493,11 +498,21 @@ public class AppStandbyController {
if ((event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND
|| event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND
|| event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION
- || event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
- mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
+ || event.mEventType == UsageEvents.Event.USER_INTERACTION
+ || event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN)) {
+
+ final int newBucket;
+ if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN) {
+ newBucket = mAppIdleHistory.reportMildUsage(event.mPackage, userId,
+ elapsedRealtime);
+ } else {
+ newBucket = mAppIdleHistory.reportUsage(event.mPackage, userId,
+ elapsedRealtime);
+ }
+
+ maybeInformListeners(event.mPackage, userId, elapsedRealtime,
+ newBucket);
if (previouslyIdle) {
- maybeInformListeners(event.mPackage, userId, elapsedRealtime,
- AppStandby.STANDBY_BUCKET_ACTIVE);
notifyBatteryStats(event.mPackage, userId, false);
}
}
@@ -519,15 +534,15 @@ public class AppStandbyController {
final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
userId, elapsedRealtime);
+ final int standbyBucket;
synchronized (mAppIdleLock) {
- mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
+ standbyBucket = mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
}
final boolean stillIdle = isAppIdleFiltered(packageName, appId,
userId, elapsedRealtime);
// Inform listeners if necessary
if (previouslyIdle != stillIdle) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
- /* idle = */ stillIdle ? 1 : 0, packageName));
+ maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket);
if (!stillIdle) {
notifyBatteryStats(packageName, userId, idle);
}
@@ -723,7 +738,7 @@ public class AppStandbyController {
.sendToTarget();
}
- @StandbyBuckets int getAppStandbyBucket(String packageName, int userId,
+ @StandbyBuckets public int getAppStandbyBucket(String packageName, int userId,
long elapsedRealtime, boolean shouldObfuscateInstantApps) {
if (shouldObfuscateInstantApps &&
mInjector.isPackageEphemeral(userId, packageName)) {
@@ -737,6 +752,8 @@ public class AppStandbyController {
String reason, long elapsedRealtime) {
mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket,
reason);
+ maybeInformListeners(packageName, userId, elapsedRealtime,
+ newBucket);
}
private boolean isActiveDeviceAdmin(String packageName, int userId) {
@@ -896,14 +913,6 @@ public class AppStandbyController {
pw.println();
pw.println("Settings:");
- pw.print(" mAppIdleDurationMillis=");
- TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
- pw.println();
-
- pw.print(" mAppIdleWallclockThresholdMillis=");
- TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw);
- pw.println();
-
pw.print(" mCheckIdleIntervalMillis=");
TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
pw.println();
@@ -1033,6 +1042,11 @@ public class AppStandbyController {
int userId) {
return appWidgetManager.isBoundWidgetPackage(packageName, userId);
}
+
+ String getAppIdleSettings() {
+ return Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.APP_IDLE_CONSTANTS);
+ }
}
class AppStandbyHandler extends Handler {
@@ -1165,31 +1179,18 @@ public class AppStandbyController {
// Look at global settings for this.
// TODO: Maybe apply different thresholds for different users.
try {
- mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.APP_IDLE_CONSTANTS));
+ mParser.setString(mInjector.getAppIdleSettings());
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
// fallthrough, mParser is empty and all defaults will be returned.
}
- // Default: 12 hours of screen-on time sans dream-time
- mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
- COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
-
- mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
- COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
-
- mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
- COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours
-
// Default: 24 hours between paroles
mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
- mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
- mAppIdleScreenThresholdMillis);
String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null);
mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
@@ -1199,6 +1200,9 @@ public class AppStandbyController {
null);
mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue,
ELAPSED_TIME_THRESHOLDS);
+ mCheckIdleIntervalMillis = Math.min(mAppStandbyElapsedThresholds[1] / 4,
+ COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours
+
}
}
diff --git a/com/android/server/usage/UsageStatsService.java b/com/android/server/usage/UsageStatsService.java
index 44e6a6cb..65c1cefa 100644
--- a/com/android/server/usage/UsageStatsService.java
+++ b/com/android/server/usage/UsageStatsService.java
@@ -24,6 +24,7 @@ import android.app.usage.AppStandby;
import android.app.usage.ConfigurationStats;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
+import android.app.usage.AppStandby.StandbyBuckets;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManagerInternal;
@@ -867,6 +868,12 @@ public class UsageStatsService extends SystemService implements
}
@Override
+ @StandbyBuckets public int getAppStandbyBucket(String packageName, int userId,
+ long nowElapsed) {
+ return mAppStandby.getAppStandbyBucket(packageName, userId, nowElapsed, false);
+ }
+
+ @Override
public int[] getIdleUidsForUser(int userId) {
return mAppStandby.getIdleUidsForUser(userId);
}
diff --git a/com/android/server/usage/UserUsageStatsService.java b/com/android/server/usage/UserUsageStatsService.java
index 0b105904..f02221cb 100644
--- a/com/android/server/usage/UserUsageStatsService.java
+++ b/com/android/server/usage/UserUsageStatsService.java
@@ -643,6 +643,8 @@ class UserUsageStatsService {
return "SHORTCUT_INVOCATION";
case UsageEvents.Event.CHOOSER_ACTION:
return "CHOOSER_ACTION";
+ case UsageEvents.Event.NOTIFICATION_SEEN:
+ return "NOTIFICATION_SEEN";
default:
return "UNKNOWN";
}
diff --git a/com/android/server/usb/UsbAlsaManager.java b/com/android/server/usb/UsbAlsaManager.java
index d359b704..7bea8a11 100644
--- a/com/android/server/usb/UsbAlsaManager.java
+++ b/com/android/server/usb/UsbAlsaManager.java
@@ -255,6 +255,7 @@ public final class UsbAlsaManager {
}
private void alsaFileAdded(String name) {
+ Slog.i(TAG, "alsaFileAdded(" + name + ")");
int type = AlsaDevice.TYPE_UNKNOWN;
int card = -1, device = -1;
diff --git a/com/android/server/usb/UsbHostManager.java b/com/android/server/usb/UsbHostManager.java
index 095fdc63..dd2e1929 100644
--- a/com/android/server/usb/UsbHostManager.java
+++ b/com/android/server/usb/UsbHostManager.java
@@ -19,13 +19,8 @@ package com.android.server.usb;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
-import android.hardware.usb.UsbConfiguration;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbDeviceConnection;
-import android.hardware.usb.UsbEndpoint;
-import android.hardware.usb.UsbInterface;
-import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
@@ -37,7 +32,6 @@ import com.android.server.usb.descriptors.UsbDescriptorParser;
import com.android.server.usb.descriptors.report.TextReportCanvas;
import com.android.server.usb.descriptors.tree.UsbDescriptorsTree;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -50,28 +44,23 @@ public class UsbHostManager {
private final Context mContext;
- // contains all connected USB devices
- private final HashMap<String, UsbDevice> mDevices = new HashMap<>();
-
// USB busses to exclude from USB host support
private final String[] mHostBlacklist;
- private final Object mLock = new Object();
-
- private UsbDevice mNewDevice;
- private UsbConfiguration mNewConfiguration;
- private UsbInterface mNewInterface;
- private ArrayList<UsbConfiguration> mNewConfigurations;
- private ArrayList<UsbInterface> mNewInterfaces;
- private ArrayList<UsbEndpoint> mNewEndpoints;
-
private final UsbAlsaManager mUsbAlsaManager;
private final UsbSettingsManager mSettingsManager;
+ private final Object mLock = new Object();
@GuardedBy("mLock")
+ // contains all connected USB devices
+ private final HashMap<String, UsbDevice> mDevices = new HashMap<>();
+
+ private Object mSettingsLock = new Object();
+ @GuardedBy("mSettingsLock")
private UsbProfileGroupSettingsManager mCurrentSettings;
- @GuardedBy("mLock")
+ private Object mHandlerLock = new Object();
+ @GuardedBy("mHandlerLock")
private ComponentName mUsbDeviceConnectionHandler;
public UsbHostManager(Context context, UsbAlsaManager alsaManager,
@@ -91,33 +80,33 @@ public class UsbHostManager {
}
public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) {
- synchronized (mLock) {
+ synchronized (mSettingsLock) {
mCurrentSettings = settings;
}
}
private UsbProfileGroupSettingsManager getCurrentUserSettings() {
- synchronized (mLock) {
+ synchronized (mSettingsLock) {
return mCurrentSettings;
}
}
public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
- synchronized (mLock) {
+ synchronized (mHandlerLock) {
mUsbDeviceConnectionHandler = usbDeviceConnectionHandler;
}
}
private @Nullable ComponentName getUsbDeviceConnectionHandler() {
- synchronized (mLock) {
+ synchronized (mHandlerLock) {
return mUsbDeviceConnectionHandler;
}
}
- private boolean isBlackListed(String deviceName) {
+ private boolean isBlackListed(String deviceAddress) {
int count = mHostBlacklist.length;
for (int i = 0; i < count; i++) {
- if (deviceName.startsWith(mHostBlacklist[i])) {
+ if (deviceAddress.startsWith(mHostBlacklist[i])) {
return true;
}
}
@@ -136,166 +125,73 @@ public class UsbHostManager {
}
/* Called from JNI in monitorUsbHostBus() to report new USB devices
- Returns true if successful, in which case the JNI code will continue adding configurations,
- interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors
- have been processed
+ Returns true if successful, i.e. the USB Audio device descriptors are
+ correctly parsed and the unique device is added to the audio device list.
*/
@SuppressWarnings("unused")
- private boolean beginUsbDeviceAdded(String deviceName, int vendorID, int productID,
- int deviceClass, int deviceSubclass, int deviceProtocol,
- String manufacturerName, String productName, int version, String serialNumber) {
-
+ private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass,
+ byte[] descriptors) {
if (DEBUG) {
- Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")");
- // Audio Class Codes:
- // Audio: 0x01
- // Audio Subclass Codes:
- // undefined: 0x00
- // audio control: 0x01
- // audio streaming: 0x02
- // midi streaming: 0x03
-
- // some useful debugging info
- Slog.d(TAG, "usb: nm:" + deviceName + " vnd:" + vendorID + " prd:" + productID + " cls:"
- + deviceClass + " sub:" + deviceSubclass + " proto:" + deviceProtocol);
+ Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start");
}
- // OK this is non-obvious, but true. One can't tell if the device being attached is even
- // potentially an audio device without parsing the interface descriptors, so punt on any
- // such test until endUsbDeviceAdded() when we have that info.
-
- if (isBlackListed(deviceName) ||
- isBlackListed(deviceClass, deviceSubclass)) {
+ // check class/subclass first as it is more likely to be blacklisted
+ if (isBlackListed(deviceClass, deviceSubclass) || isBlackListed(deviceAddress)) {
+ if (DEBUG) {
+ Slog.d(TAG, "device is black listed");
+ }
return false;
}
synchronized (mLock) {
- if (mDevices.get(deviceName) != null) {
- Slog.w(TAG, "device already on mDevices list: " + deviceName);
+ if (mDevices.get(deviceAddress) != null) {
+ Slog.w(TAG, "device already on mDevices list: " + deviceAddress);
+ //TODO If this is the same peripheral as is being connected, replace
+ // it with the new connection.
return false;
}
- if (mNewDevice != null) {
- Slog.e(TAG, "mNewDevice is not null in endUsbDeviceAdded");
- return false;
- }
-
- // Create version string in "%.%" format
- String versionString = Integer.toString(version >> 8) + "." + (version & 0xFF);
+ UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress);
+ if (parser.parseDescriptors(descriptors)) {
- mNewDevice = new UsbDevice(deviceName, vendorID, productID,
- deviceClass, deviceSubclass, deviceProtocol,
- manufacturerName, productName, versionString, serialNumber);
-
- mNewConfigurations = new ArrayList<>();
- mNewInterfaces = new ArrayList<>();
- mNewEndpoints = new ArrayList<>();
- }
-
- return true;
- }
-
- /* Called from JNI in monitorUsbHostBus() to report new USB configuration for the device
- currently being added. Returns true if successful, false in case of error.
- */
- @SuppressWarnings("unused")
- private void addUsbConfiguration(int id, String name, int attributes, int maxPower) {
- if (mNewConfiguration != null) {
- mNewConfiguration.setInterfaces(
- mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
- mNewInterfaces.clear();
- }
-
- mNewConfiguration = new UsbConfiguration(id, name, attributes, maxPower);
- mNewConfigurations.add(mNewConfiguration);
- }
-
- /* Called from JNI in monitorUsbHostBus() to report new USB interface for the device
- currently being added. Returns true if successful, false in case of error.
- */
- @SuppressWarnings("unused")
- private void addUsbInterface(int id, String name, int altSetting,
- int Class, int subClass, int protocol) {
- if (mNewInterface != null) {
- mNewInterface.setEndpoints(
- mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
- mNewEndpoints.clear();
- }
-
- mNewInterface = new UsbInterface(id, altSetting, name, Class, subClass, protocol);
- mNewInterfaces.add(mNewInterface);
- }
-
- /* Called from JNI in monitorUsbHostBus() to report new USB endpoint for the device
- currently being added. Returns true if successful, false in case of error.
- */
- @SuppressWarnings("unused")
- private void addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval) {
- mNewEndpoints.add(new UsbEndpoint(address, attributes, maxPacketSize, interval));
- }
-
- /* Called from JNI in monitorUsbHostBus() to finish adding a new device */
- @SuppressWarnings("unused")
- private void endUsbDeviceAdded() {
- if (DEBUG) {
- Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()");
- }
- if (mNewInterface != null) {
- mNewInterface.setEndpoints(
- mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
- }
- if (mNewConfiguration != null) {
- mNewConfiguration.setInterfaces(
- mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
- }
-
-
- synchronized (mLock) {
- if (mNewDevice != null) {
- mNewDevice.setConfigurations(
- mNewConfigurations.toArray(
- new UsbConfiguration[mNewConfigurations.size()]));
- mDevices.put(mNewDevice.getDeviceName(), mNewDevice);
- Slog.d(TAG, "Added device " + mNewDevice);
+ UsbDevice newDevice = parser.toAndroidUsbDevice();
+ mDevices.put(deviceAddress, newDevice);
// It is fine to call this only for the current user as all broadcasts are sent to
// all profiles of the user and the dialogs should only show once.
ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
if (usbDeviceConnectionHandler == null) {
- getCurrentUserSettings().deviceAttached(mNewDevice);
+ getCurrentUserSettings().deviceAttached(newDevice);
} else {
- getCurrentUserSettings().deviceAttachedForFixedHandler(mNewDevice,
+ getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
usbDeviceConnectionHandler);
}
- // deviceName is something like: "/dev/bus/usb/001/001"
- UsbDescriptorParser parser = new UsbDescriptorParser();
- boolean isInputHeadset = false;
- boolean isOutputHeadset = false;
- if (parser.parseDevice(mNewDevice.getDeviceName())) {
- isInputHeadset = parser.isInputHeadset();
- isOutputHeadset = parser.isOutputHeadset();
- Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset
- + " , out: " + isOutputHeadset + "]");
- }
- mUsbAlsaManager.usbDeviceAdded(mNewDevice,
- isInputHeadset, isOutputHeadset);
+
+ // Headset?
+ boolean isInputHeadset = parser.isInputHeadset();
+ boolean isOutputHeadset = parser.isOutputHeadset();
+ Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset
+ + " , out: " + isOutputHeadset + "]");
+
+ mUsbAlsaManager.usbDeviceAdded(newDevice, isInputHeadset, isOutputHeadset);
} else {
- Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
+ Slog.e(TAG, "Error parsing USB device descriptors for " + deviceAddress);
+ return false;
}
- mNewDevice = null;
- mNewConfigurations = null;
- mNewInterfaces = null;
- mNewEndpoints = null;
- mNewConfiguration = null;
- mNewInterface = null;
}
+
+ if (DEBUG) {
+ Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end");
+ }
+
+ return true;
}
/* Called from JNI in monitorUsbHostBus to report USB device removal */
@SuppressWarnings("unused")
- private void usbDeviceRemoved(String deviceName) {
+ private void usbDeviceRemoved(String deviceAddress) {
synchronized (mLock) {
- UsbDevice device = mDevices.remove(deviceName);
+ UsbDevice device = mDevices.remove(deviceAddress);
if (device != null) {
mUsbAlsaManager.usbDeviceRemoved(device);
mSettingsManager.usbDeviceRemoved(device);
@@ -323,31 +219,35 @@ public class UsbHostManager {
}
/* Opens the specified USB device */
- public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) {
+ public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings,
+ String packageName, int uid) {
synchronized (mLock) {
- if (isBlackListed(deviceName)) {
+ if (isBlackListed(deviceAddress)) {
throw new SecurityException("USB device is on a restricted bus");
}
- UsbDevice device = mDevices.get(deviceName);
+ UsbDevice device = mDevices.get(deviceAddress);
if (device == null) {
// if it is not in mDevices, it either does not exist or is blacklisted
throw new IllegalArgumentException(
- "device " + deviceName + " does not exist or is restricted");
+ "device " + deviceAddress + " does not exist or is restricted");
}
- settings.checkPermission(device);
- return nativeOpenDevice(deviceName);
+
+ settings.checkPermission(device, packageName, uid);
+ return nativeOpenDevice(deviceAddress);
}
}
public void dump(IndentingPrintWriter pw) {
+ pw.println("USB Host State:");
+ synchronized (mHandlerLock) {
+ if (mUsbDeviceConnectionHandler != null) {
+ pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler);
+ }
+ }
synchronized (mLock) {
- pw.println("USB Host State:");
for (String name : mDevices.keySet()) {
pw.println(" " + name + ": " + mDevices.get(name));
}
- if (mUsbDeviceConnectionHandler != null) {
- pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler);
- }
Collection<UsbDevice> devices = mDevices.values();
if (devices.size() != 0) {
@@ -355,17 +255,12 @@ public class UsbHostManager {
for (UsbDevice device : devices) {
StringBuilder stringBuilder = new StringBuilder();
- UsbDescriptorParser parser = new UsbDescriptorParser();
- if (parser.parseDevice(device.getDeviceName())) {
+ UsbDescriptorParser parser = new UsbDescriptorParser(device.getDeviceName());
+ if (parser.parseDevice()) {
UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree();
descriptorTree.parse(parser);
- UsbManager usbManager =
- (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
- UsbDeviceConnection connection = usbManager.openDevice(device);
-
- descriptorTree.report(new TextReportCanvas(connection, stringBuilder));
- connection.close();
+ descriptorTree.report(new TextReportCanvas(parser, stringBuilder));
stringBuilder.append("isHeadset[in: " + parser.isInputHeadset()
+ " , out: " + parser.isOutputHeadset() + "]");
@@ -381,5 +276,5 @@ public class UsbHostManager {
}
private native void monitorUsbHostBus();
- private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
+ private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
}
diff --git a/com/android/server/usb/UsbService.java b/com/android/server/usb/UsbService.java
index e4fcea77..17de83f0 100644
--- a/com/android/server/usb/UsbService.java
+++ b/com/android/server/usb/UsbService.java
@@ -232,7 +232,7 @@ public class UsbService extends IUsbManager.Stub {
/* Opens the specified USB device (host mode) */
@Override
- public ParcelFileDescriptor openDevice(String deviceName) {
+ public ParcelFileDescriptor openDevice(String deviceName, String packageName) {
ParcelFileDescriptor fd = null;
if (mHostManager != null) {
@@ -242,7 +242,8 @@ public class UsbService extends IUsbManager.Stub {
boolean isCurrentUser = isCallerInCurrentUserProfileGroupLocked();
if (isCurrentUser) {
- fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt));
+ fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt),
+ packageName, Binder.getCallingUid());
} else {
Slog.w(TAG, "Cannot open " + deviceName + " for user " + userIdInt +
" as user is not active.");
@@ -308,9 +309,10 @@ public class UsbService extends IUsbManager.Stub {
}
@Override
- public boolean hasDevicePermission(UsbDevice device) {
+ public boolean hasDevicePermission(UsbDevice device, String packageName) {
final int userId = UserHandle.getCallingUserId();
- return getSettingsForUser(userId).hasPermission(device);
+ return getSettingsForUser(userId).hasPermission(device, packageName,
+ Binder.getCallingUid());
}
@Override
@@ -322,7 +324,8 @@ public class UsbService extends IUsbManager.Stub {
@Override
public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
final int userId = UserHandle.getCallingUserId();
- getSettingsForUser(userId).requestPermission(device, packageName, pi);
+ getSettingsForUser(userId).requestPermission(device, packageName, pi,
+ Binder.getCallingUid());
}
@Override
diff --git a/com/android/server/usb/UsbUserSettingsManager.java b/com/android/server/usb/UsbUserSettingsManager.java
index 96c5211c..11e43e30 100644
--- a/com/android/server/usb/UsbUserSettingsManager.java
+++ b/com/android/server/usb/UsbUserSettingsManager.java
@@ -26,6 +26,8 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbManager;
import android.os.Binder;
import android.os.Process;
@@ -95,10 +97,70 @@ class UsbUserSettingsManager {
}
}
+ /**
+ * Check whether a particular device or any of its interfaces
+ * is of class VIDEO.
+ *
+ * @param device The device that needs to get scanned
+ * @return True in case a VIDEO device or interface is present,
+ * False otherwise.
+ */
+ private boolean isCameraDevicePresent(UsbDevice device) {
+ if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) {
+ return true;
+ }
+
+ for (int i = 0; i < device.getInterfaceCount(); i++) {
+ UsbInterface iface = device.getInterface(i);
+ if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VIDEO) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check for camera permission of the calling process.
+ *
+ * @param packageName Package name of the caller.
+ * @param uid Linux uid of the calling process.
+ *
+ * @return True in case camera permission is available, False otherwise.
+ */
+ private boolean isCameraPermissionGranted(String packageName, int uid) {
+ int targetSdkVersion = android.os.Build.VERSION_CODES.P;
+ try {
+ ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
+ // compare uid with packageName to foil apps pretending to be someone else
+ if (aInfo.uid != uid) {
+ Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid);
+ return false;
+ }
+ targetSdkVersion = aInfo.targetSdkVersion;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.i(TAG, "Package not found, likely due to invalid package name!");
+ return false;
+ }
+
+ if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) {
+ int allowed = mUserContext.checkCallingPermission(android.Manifest.permission.CAMERA);
+ if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) {
+ Slog.i(TAG, "Camera permission required for USB video class devices");
+ return false;
+ }
+ }
- public boolean hasPermission(UsbDevice device) {
+ return true;
+ }
+
+ public boolean hasPermission(UsbDevice device, String packageName, int uid) {
synchronized (mLock) {
- int uid = Binder.getCallingUid();
+ if (isCameraDevicePresent(device)) {
+ if (!isCameraPermissionGranted(packageName, uid)) {
+ return false;
+ }
+ }
if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
return true;
}
@@ -124,8 +186,8 @@ class UsbUserSettingsManager {
}
}
- public void checkPermission(UsbDevice device) {
- if (!hasPermission(device)) {
+ public void checkPermission(UsbDevice device, String packageName, int uid) {
+ if (!hasPermission(device, packageName, uid)) {
throw new SecurityException("User has not given permission to device " + device);
}
}
@@ -166,11 +228,11 @@ class UsbUserSettingsManager {
}
}
- public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) {
+ public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid) {
Intent intent = new Intent();
// respond immediately if permission has already been granted
- if (hasPermission(device)) {
+ if (hasPermission(device, packageName, uid)) {
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
try {
@@ -180,6 +242,18 @@ class UsbUserSettingsManager {
}
return;
}
+ if (isCameraDevicePresent(device)) {
+ if (!isCameraPermissionGranted(packageName, uid)) {
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
+ try {
+ pi.send(mUserContext, 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
+ }
+ return;
+ }
+ }
// start UsbPermissionActivity so user can choose an activity
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
diff --git a/com/android/server/usb/descriptors/Usb10ACHeader.java b/com/android/server/usb/descriptors/Usb10ACHeader.java
index a35b4631..7763150a 100644
--- a/com/android/server/usb/descriptors/Usb10ACHeader.java
+++ b/com/android/server/usb/descriptors/Usb10ACHeader.java
@@ -32,7 +32,7 @@ public final class Usb10ACHeader extends UsbACHeaderInterface {
// numbers associate with this endpoint
private byte mControls; // Vers 2.0 thing
- public Usb10ACHeader(int length, byte type, byte subtype, byte subclass, int spec) {
+ public Usb10ACHeader(int length, byte type, byte subtype, int subclass, int spec) {
super(length, type, subtype, subclass, spec);
}
diff --git a/com/android/server/usb/descriptors/Usb10ACInputTerminal.java b/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
index 2363c4dd..75531d17 100644
--- a/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
+++ b/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
@@ -32,7 +32,7 @@ public final class Usb10ACInputTerminal extends UsbACTerminal {
private byte mChannelNames; // 10:1 Unused (0x00)
private byte mTerminal; // 11:1 Unused (0x00)
- public Usb10ACInputTerminal(int length, byte type, byte subtype, byte subclass) {
+ public Usb10ACInputTerminal(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb10ACMixerUnit.java b/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
index d3486643..c7634ba7 100644
--- a/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
+++ b/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
@@ -30,7 +30,7 @@ public final class Usb10ACMixerUnit extends UsbACMixerUnit {
private byte[] mControls; // bitmasks of which controls are present for each channel
private byte mNameID; // string descriptor ID of mixer name
- public Usb10ACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+ public Usb10ACMixerUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java b/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
index 9f2f09ec..468ae571 100644
--- a/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
+++ b/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
@@ -28,7 +28,7 @@ public final class Usb10ACOutputTerminal extends UsbACTerminal {
private byte mSourceID; // 7:1 From Input Terminal. (0x01)
private byte mTerminal; // 8:1 Unused.
- public Usb10ACOutputTerminal(int length, byte type, byte subtype, byte subClass) {
+ public Usb10ACOutputTerminal(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/Usb10ASFormatI.java b/com/android/server/usb/descriptors/Usb10ASFormatI.java
index 1523bb52..1d8498ae 100644
--- a/com/android/server/usb/descriptors/Usb10ASFormatI.java
+++ b/com/android/server/usb/descriptors/Usb10ASFormatI.java
@@ -33,7 +33,7 @@ public final class Usb10ASFormatI extends UsbASFormat {
// min & max rates otherwise mSamFreqType rates.
// All 3-byte values. All rates in Hz
- public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb10ASFormatII.java b/com/android/server/usb/descriptors/Usb10ASFormatII.java
index b1e7680e..3c45790a 100644
--- a/com/android/server/usb/descriptors/Usb10ASFormatII.java
+++ b/com/android/server/usb/descriptors/Usb10ASFormatII.java
@@ -38,7 +38,7 @@ public final class Usb10ASFormatII extends UsbASFormat {
// the min & max rates. otherwise mSamFreqType rates.
// All 3-byte values. All rates in Hz
- public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb10ASGeneral.java b/com/android/server/usb/descriptors/Usb10ASGeneral.java
index 2d4f604e..4fbbb213 100644
--- a/com/android/server/usb/descriptors/Usb10ASGeneral.java
+++ b/com/android/server/usb/descriptors/Usb10ASGeneral.java
@@ -34,7 +34,7 @@ public final class Usb10ASGeneral extends UsbACInterface {
private int mFormatTag; // 5:2 The Audio Data Format that has to be used to communicate
// with this interface.
- public Usb10ASGeneral(int length, byte type, byte subtype, byte subclass) {
+ public Usb10ASGeneral(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ACHeader.java b/com/android/server/usb/descriptors/Usb20ACHeader.java
index eefae3d5..fe1b5023 100644
--- a/com/android/server/usb/descriptors/Usb20ACHeader.java
+++ b/com/android/server/usb/descriptors/Usb20ACHeader.java
@@ -29,7 +29,7 @@ public final class Usb20ACHeader extends UsbACHeaderInterface {
// See audio20.pdf Appendix A.7, “Audio Function Category Codes.”
private byte mControls; // 8:1 See audio20.pdf Table 4-5.
- public Usb20ACHeader(int length, byte type, byte subtype, byte subclass, int spec) {
+ public Usb20ACHeader(int length, byte type, byte subtype, int subclass, int spec) {
super(length, type, subtype, subclass, spec);
}
diff --git a/com/android/server/usb/descriptors/Usb20ACInputTerminal.java b/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
index 3e2ac39c..ee1b32c1 100644
--- a/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
+++ b/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
@@ -39,7 +39,7 @@ public final class Usb20ACInputTerminal extends UsbACTerminal {
private byte mTerminalName; // 16:1 - Index of a string descriptor, describing the
// Input Terminal.
- public Usb20ACInputTerminal(int length, byte type, byte subtype, byte subclass) {
+ public Usb20ACInputTerminal(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ACMixerUnit.java b/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
index 1b267a67..ab965856 100644
--- a/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
+++ b/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
@@ -33,7 +33,7 @@ public final class Usb20ACMixerUnit extends UsbACMixerUnit {
private byte mNameID; // 12+p+N:1 Index of a string descriptor, describing the
// Mixer Unit.
- public Usb20ACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+ public Usb20ACMixerUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java b/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
index 67478aad..20a97af4 100644
--- a/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
+++ b/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
@@ -34,7 +34,7 @@ public final class Usb20ACOutputTerminal extends UsbACTerminal {
private int mControls; // 9:2 - see Audio20.pdf Table 4-10
private byte mTerminalID; // 11:1 - Index of a string descriptor, describing the
- public Usb20ACOutputTerminal(int length, byte type, byte subtype, byte subClass) {
+ public Usb20ACOutputTerminal(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ASFormatI.java b/com/android/server/usb/descriptors/Usb20ASFormatI.java
index c0319961..7310a3e2 100644
--- a/com/android/server/usb/descriptors/Usb20ASFormatI.java
+++ b/com/android/server/usb/descriptors/Usb20ASFormatI.java
@@ -31,7 +31,7 @@ public final class Usb20ASFormatI extends UsbASFormat {
private byte mBitResolution; // 5:1 The number of effectively used bits from
// the available bits in an audio subslot.
- public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ASFormatII.java b/com/android/server/usb/descriptors/Usb20ASFormatII.java
index dc44ff06..fe887432 100644
--- a/com/android/server/usb/descriptors/Usb20ASFormatII.java
+++ b/com/android/server/usb/descriptors/Usb20ASFormatII.java
@@ -34,7 +34,7 @@ public final class Usb20ASFormatII extends UsbASFormat {
/**
* TBD
*/
- public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ASFormatIII.java b/com/android/server/usb/descriptors/Usb20ASFormatIII.java
index b44a2167..b0ba02ff 100644
--- a/com/android/server/usb/descriptors/Usb20ASFormatIII.java
+++ b/com/android/server/usb/descriptors/Usb20ASFormatIII.java
@@ -31,7 +31,7 @@ public final class Usb20ASFormatIII extends UsbASFormat {
private byte mBitResolution; // 5:1 The number of effectively used bits from
// the available bits in an audio subframe.
- public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ASGeneral.java b/com/android/server/usb/descriptors/Usb20ASGeneral.java
index 18d48a00..de207388 100644
--- a/com/android/server/usb/descriptors/Usb20ASGeneral.java
+++ b/com/android/server/usb/descriptors/Usb20ASGeneral.java
@@ -41,7 +41,7 @@ public final class Usb20ASGeneral extends UsbACInterface {
private byte mChannelNames; // 15:1 Index of a string descriptor, describing the
// name of the first physical channel.
- public Usb20ASGeneral(int length, byte type, byte subtype, byte subclass) {
+ public Usb20ASGeneral(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
index 6e1ce075..409e605c 100644
--- a/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
+++ b/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
@@ -38,7 +38,7 @@ public class UsbACAudioControlEndpoint extends UsbACEndpoint {
static final byte ATTRIBSMASK_SYNC = 0x0C;
static final byte ATTRIBMASK_TRANS = 0x03;
- public UsbACAudioControlEndpoint(int length, byte type, byte subclass) {
+ public UsbACAudioControlEndpoint(int length, byte type, int subclass) {
super(length, type, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
index d3519029..e63bb74a 100644
--- a/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
+++ b/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
@@ -24,7 +24,7 @@ public class UsbACAudioStreamEndpoint extends UsbACEndpoint {
private static final String TAG = "UsbACAudioStreamEndpoint";
//TODO data fields...
- public UsbACAudioStreamEndpoint(int length, byte type, byte subclass) {
+ public UsbACAudioStreamEndpoint(int length, byte type, int subclass) {
super(length, type, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbACEndpoint.java b/com/android/server/usb/descriptors/UsbACEndpoint.java
index 4a6839d9..7ebccf39 100644
--- a/com/android/server/usb/descriptors/UsbACEndpoint.java
+++ b/com/android/server/usb/descriptors/UsbACEndpoint.java
@@ -25,16 +25,16 @@ import android.util.Log;
abstract class UsbACEndpoint extends UsbDescriptor {
private static final String TAG = "UsbACEndpoint";
- protected final byte mSubclass; // from the mSubclass member of the "enclosing"
- // Interface Descriptor, not the stream.
- protected byte mSubtype; // 2:1 HEADER descriptor subtype
+ protected final int mSubclass; // from the mSubclass member of the "enclosing"
+ // Interface Descriptor, not the stream.
+ protected byte mSubtype; // 2:1 HEADER descriptor subtype
- UsbACEndpoint(int length, byte type, byte subclass) {
+ UsbACEndpoint(int length, byte type, int subclass) {
super(length, type);
mSubclass = subclass;
}
- public byte getSubclass() {
+ public int getSubclass() {
return mSubclass;
}
@@ -52,7 +52,7 @@ abstract class UsbACEndpoint extends UsbDescriptor {
public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
int length, byte type) {
UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
- byte subClass = interfaceDesc.getUsbSubclass();
+ int subClass = interfaceDesc.getUsbSubclass();
switch (subClass) {
case AUDIO_AUDIOCONTROL:
return new UsbACAudioControlEndpoint(length, type, subClass);
diff --git a/com/android/server/usb/descriptors/UsbACFeatureUnit.java b/com/android/server/usb/descriptors/UsbACFeatureUnit.java
index ab3903b4..2c7ef798 100644
--- a/com/android/server/usb/descriptors/UsbACFeatureUnit.java
+++ b/com/android/server/usb/descriptors/UsbACFeatureUnit.java
@@ -46,7 +46,7 @@ public final class UsbACFeatureUnit extends UsbACInterface {
// logical channel
private byte mUnitName; // ?:1 Index of a string descriptor, describing this Feature Unit.
- public UsbACFeatureUnit(int length, byte type, byte subtype, byte subClass) {
+ public UsbACFeatureUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/UsbACHeaderInterface.java b/com/android/server/usb/descriptors/UsbACHeaderInterface.java
index 01a355e2..88d026eb 100644
--- a/com/android/server/usb/descriptors/UsbACHeaderInterface.java
+++ b/com/android/server/usb/descriptors/UsbACHeaderInterface.java
@@ -31,7 +31,7 @@ public abstract class UsbACHeaderInterface extends UsbACInterface {
// of this descriptor header and all Unit and Terminal descriptors.
public UsbACHeaderInterface(
- int length, byte type, byte subtype, byte subclass, int adcRelease) {
+ int length, byte type, byte subtype, int subclass, int adcRelease) {
super(length, type, subtype, subclass);
mADCRelease = adcRelease;
}
diff --git a/com/android/server/usb/descriptors/UsbACInterface.java b/com/android/server/usb/descriptors/UsbACInterface.java
index df6c53fa..38c12a1f 100644
--- a/com/android/server/usb/descriptors/UsbACInterface.java
+++ b/com/android/server/usb/descriptors/UsbACInterface.java
@@ -78,10 +78,10 @@ public abstract class UsbACInterface extends UsbDescriptor {
public static final int FORMAT_III_IEC1937_MPEG2_Layer1LS = 0x2005;
protected final byte mSubtype; // 2:1 HEADER descriptor subtype
- protected final byte mSubclass; // from the mSubclass member of the
+ protected final int mSubclass; // from the mSubclass member of the
// "enclosing" Interface Descriptor
- public UsbACInterface(int length, byte type, byte subtype, byte subclass) {
+ public UsbACInterface(int length, byte type, byte subtype, int subclass) {
super(length, type);
mSubtype = subtype;
mSubclass = subclass;
@@ -91,12 +91,12 @@ public abstract class UsbACInterface extends UsbDescriptor {
return mSubtype;
}
- public byte getSubclass() {
+ public int getSubclass() {
return mSubclass;
}
private static UsbDescriptor allocAudioControlDescriptor(UsbDescriptorParser parser,
- ByteStream stream, int length, byte type, byte subtype, byte subClass) {
+ ByteStream stream, int length, byte type, byte subtype, int subClass) {
switch (subtype) {
case ACI_HEADER:
{
@@ -157,7 +157,7 @@ public abstract class UsbACInterface extends UsbDescriptor {
}
private static UsbDescriptor allocAudioStreamingDescriptor(UsbDescriptorParser parser,
- ByteStream stream, int length, byte type, byte subtype, byte subClass) {
+ ByteStream stream, int length, byte type, byte subtype, int subClass) {
//int spec = parser.getUsbSpec();
int acInterfaceSpec = parser.getACInterfaceSpec();
switch (subtype) {
@@ -182,7 +182,7 @@ public abstract class UsbACInterface extends UsbDescriptor {
}
private static UsbDescriptor allocMidiStreamingDescriptor(int length, byte type,
- byte subtype, byte subClass) {
+ byte subtype, int subClass) {
switch (subtype) {
case MSI_HEADER:
return new UsbMSMidiHeader(length, type, subtype, subClass);
@@ -212,7 +212,7 @@ public abstract class UsbACInterface extends UsbDescriptor {
int length, byte type) {
byte subtype = stream.getByte();
UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
- byte subClass = interfaceDesc.getUsbSubclass();
+ int subClass = interfaceDesc.getUsbSubclass();
switch (subClass) {
case AUDIO_AUDIOCONTROL:
return allocAudioControlDescriptor(
@@ -236,7 +236,7 @@ public abstract class UsbACInterface extends UsbDescriptor {
public void report(ReportCanvas canvas) {
super.report(canvas);
- byte subClass = getSubclass();
+ int subClass = getSubclass();
String subClassName = UsbStrings.getACInterfaceSubclassName(subClass);
byte subtype = getSubtype();
diff --git a/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java b/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
index 9e00a797..bd027aeb 100644
--- a/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
+++ b/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
@@ -22,7 +22,7 @@ package com.android.server.usb.descriptors;
public final class UsbACInterfaceUnparsed extends UsbACInterface {
private static final String TAG = "UsbACInterfaceUnparsed";
- public UsbACInterfaceUnparsed(int length, byte type, byte subtype, byte subClass) {
+ public UsbACInterfaceUnparsed(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
}
diff --git a/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
index 9c314575..42ee8892 100644
--- a/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
+++ b/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
@@ -28,7 +28,7 @@ public final class UsbACMidiEndpoint extends UsbACEndpoint {
private byte mNumJacks;
private byte[] mJackIds;
- public UsbACMidiEndpoint(int length, byte type, byte subclass) {
+ public UsbACMidiEndpoint(int length, byte type, int subclass) {
super(length, type, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbACMixerUnit.java b/com/android/server/usb/descriptors/UsbACMixerUnit.java
index 88faed96..606fa2b4 100644
--- a/com/android/server/usb/descriptors/UsbACMixerUnit.java
+++ b/com/android/server/usb/descriptors/UsbACMixerUnit.java
@@ -24,7 +24,7 @@ public class UsbACMixerUnit extends UsbACInterface {
// are connected.
protected byte mNumOutputs; // The number of output channels
- public UsbACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+ public UsbACMixerUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/UsbACSelectorUnit.java b/com/android/server/usb/descriptors/UsbACSelectorUnit.java
index b16bc575..4644fe14 100644
--- a/com/android/server/usb/descriptors/UsbACSelectorUnit.java
+++ b/com/android/server/usb/descriptors/UsbACSelectorUnit.java
@@ -32,7 +32,7 @@ public final class UsbACSelectorUnit extends UsbACInterface {
// Input Pin of this Selector Unit is connected.
private byte mNameIndex; // Index of a string descriptor, describing the Selector Unit.
- public UsbACSelectorUnit(int length, byte type, byte subtype, byte subClass) {
+ public UsbACSelectorUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/UsbACTerminal.java b/com/android/server/usb/descriptors/UsbACTerminal.java
index 28365085..36139d6c 100644
--- a/com/android/server/usb/descriptors/UsbACTerminal.java
+++ b/com/android/server/usb/descriptors/UsbACTerminal.java
@@ -32,7 +32,7 @@ public abstract class UsbACTerminal extends UsbACInterface {
protected int mTerminalType; // 4:2 USB Streaming. (0x0101)
protected byte mAssocTerminal; // 6:1 Unused (0x00)
- public UsbACTerminal(int length, byte type, byte subtype, byte subclass) {
+ public UsbACTerminal(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbASFormat.java b/com/android/server/usb/descriptors/UsbASFormat.java
index 7a92f9e1..5e515a14 100644
--- a/com/android/server/usb/descriptors/UsbASFormat.java
+++ b/com/android/server/usb/descriptors/UsbASFormat.java
@@ -40,7 +40,7 @@ public class UsbASFormat extends UsbACInterface {
public static final byte EXT_FORMAT_TYPE_II = (byte) 0x82;
public static final byte EXT_FORMAT_TYPE_III = (byte) 0x83;
- public UsbASFormat(int length, byte type, byte subtype, byte formatType, byte mSubclass) {
+ public UsbASFormat(int length, byte type, byte subtype, byte formatType, int mSubclass) {
super(length, type, subtype, mSubclass);
mFormatType = formatType;
}
@@ -66,8 +66,8 @@ public class UsbASFormat extends UsbACInterface {
* stream.
*/
public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
- ByteStream stream, int length, byte type,
- byte subtype, byte subclass) {
+ ByteStream stream, int length, byte type,
+ byte subtype, int subclass) {
byte formatType = stream.getByte();
int acInterfaceSpec = parser.getACInterfaceSpec();
diff --git a/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/com/android/server/usb/descriptors/UsbConfigDescriptor.java
index 75279c61..993778fa 100644
--- a/com/android/server/usb/descriptors/UsbConfigDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbConfigDescriptor.java
@@ -15,8 +15,13 @@
*/
package com.android.server.usb.descriptors;
+import android.hardware.usb.UsbConfiguration;
+import android.hardware.usb.UsbInterface;
+
import com.android.server.usb.descriptors.report.ReportCanvas;
+import java.util.ArrayList;
+
/**
* @hide
* An USB Config Descriptor.
@@ -25,15 +30,18 @@ import com.android.server.usb.descriptors.report.ReportCanvas;
public final class UsbConfigDescriptor extends UsbDescriptor {
private static final String TAG = "UsbConfigDescriptor";
- private int mTotalLength; // 2:2 Total length in bytes of data returned
+ private int mTotalLength; // 2:2 Total length in bytes of data returned
private byte mNumInterfaces; // 4:1 Number of Interfaces
- private byte mConfigValue; // 5:1 Value to use as an argument to select this configuration
- private byte mConfigIndex; // 6:1 Index of String Descriptor describing this configuration
- private byte mAttribs; // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered)
- // D6 Self Powered
- // D5 Remote Wakeup
- // D4..0 Reserved, set to 0.
- private byte mMaxPower; // 8:1 Maximum Power Consumption in 2mA units
+ private int mConfigValue; // 5:1 Value to use as an argument to select this configuration
+ private byte mConfigIndex; // 6:1 Index of String Descriptor describing this configuration
+ private int mAttribs; // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered)
+ // D6 Self Powered
+ // D5 Remote Wakeup
+ // D4..0 Reserved, set to 0.
+ private int mMaxPower; // 8:1 Maximum Power Consumption in 2mA units
+
+ private ArrayList<UsbInterfaceDescriptor> mInterfaceDescriptors =
+ new ArrayList<UsbInterfaceDescriptor>();
UsbConfigDescriptor(int length, byte type) {
super(length, type);
@@ -48,7 +56,7 @@ public final class UsbConfigDescriptor extends UsbDescriptor {
return mNumInterfaces;
}
- public byte getConfigValue() {
+ public int getConfigValue() {
return mConfigValue;
}
@@ -56,22 +64,38 @@ public final class UsbConfigDescriptor extends UsbDescriptor {
return mConfigIndex;
}
- public byte getAttribs() {
+ public int getAttribs() {
return mAttribs;
}
- public byte getMaxPower() {
+ public int getMaxPower() {
return mMaxPower;
}
+ void addInterfaceDescriptor(UsbInterfaceDescriptor interfaceDesc) {
+ mInterfaceDescriptors.add(interfaceDesc);
+ }
+
+ UsbConfiguration toAndroid(UsbDescriptorParser parser) {
+ String name = parser.getDescriptorString(mConfigIndex);
+ UsbConfiguration config = new
+ UsbConfiguration(mConfigValue, name, mAttribs, mMaxPower);
+ UsbInterface[] interfaces = new UsbInterface[mInterfaceDescriptors.size()];
+ for (int index = 0; index < mInterfaceDescriptors.size(); index++) {
+ interfaces[index] = mInterfaceDescriptors.get(index).toAndroid(parser);
+ }
+ config.setInterfaces(interfaces);
+ return config;
+ }
+
@Override
public int parseRawDescriptors(ByteStream stream) {
mTotalLength = stream.unpackUsbShort();
mNumInterfaces = stream.getByte();
- mConfigValue = stream.getByte();
+ mConfigValue = stream.getUnsignedByte();
mConfigIndex = stream.getByte();
- mAttribs = stream.getByte();
- mMaxPower = stream.getByte();
+ mAttribs = stream.getUnsignedByte();
+ mMaxPower = stream.getUnsignedByte();
return mLength;
}
diff --git a/com/android/server/usb/descriptors/UsbDescriptor.java b/com/android/server/usb/descriptors/UsbDescriptor.java
index 8c7565b7..3fc5fe32 100644
--- a/com/android/server/usb/descriptors/UsbDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbDescriptor.java
@@ -85,36 +85,36 @@ public abstract class UsbDescriptor implements Reporting {
public static final byte DESCRIPTORTYPE_ENDPOINT_COMPANION = 0x30; // 48
// Class IDs
- public static final byte CLASSID_DEVICE = 0x00;
- public static final byte CLASSID_AUDIO = 0x01;
- public static final byte CLASSID_COM = 0x02;
- public static final byte CLASSID_HID = 0x03;
- // public static final byte CLASSID_??? = 0x04;
- public static final byte CLASSID_PHYSICAL = 0x05;
- public static final byte CLASSID_IMAGE = 0x06;
- public static final byte CLASSID_PRINTER = 0x07;
- public static final byte CLASSID_STORAGE = 0x08;
- public static final byte CLASSID_HUB = 0x09;
- public static final byte CLASSID_CDC_CONTROL = 0x0A;
- public static final byte CLASSID_SMART_CARD = 0x0B;
- //public static final byte CLASSID_??? = 0x0C;
- public static final byte CLASSID_SECURITY = 0x0D;
- public static final byte CLASSID_VIDEO = 0x0E;
- public static final byte CLASSID_HEALTHCARE = 0x0F;
- public static final byte CLASSID_AUDIOVIDEO = 0x10;
- public static final byte CLASSID_BILLBOARD = 0x11;
- public static final byte CLASSID_TYPECBRIDGE = 0x12;
- public static final byte CLASSID_DIAGNOSTIC = (byte) 0xDC;
- public static final byte CLASSID_WIRELESS = (byte) 0xE0;
- public static final byte CLASSID_MISC = (byte) 0xEF;
- public static final byte CLASSID_APPSPECIFIC = (byte) 0xFE;
- public static final byte CLASSID_VENDSPECIFIC = (byte) 0xFF;
+ public static final int CLASSID_DEVICE = 0x00;
+ public static final int CLASSID_AUDIO = 0x01;
+ public static final int CLASSID_COM = 0x02;
+ public static final int CLASSID_HID = 0x03;
+ // public static final int CLASSID_??? = 0x04;
+ public static final int CLASSID_PHYSICAL = 0x05;
+ public static final int CLASSID_IMAGE = 0x06;
+ public static final int CLASSID_PRINTER = 0x07;
+ public static final int CLASSID_STORAGE = 0x08;
+ public static final int CLASSID_HUB = 0x09;
+ public static final int CLASSID_CDC_CONTROL = 0x0A;
+ public static final int CLASSID_SMART_CARD = 0x0B;
+ //public static final int CLASSID_??? = 0x0C;
+ public static final int CLASSID_SECURITY = 0x0D;
+ public static final int CLASSID_VIDEO = 0x0E;
+ public static final int CLASSID_HEALTHCARE = 0x0F;
+ public static final int CLASSID_AUDIOVIDEO = 0x10;
+ public static final int CLASSID_BILLBOARD = 0x11;
+ public static final int CLASSID_TYPECBRIDGE = 0x12;
+ public static final int CLASSID_DIAGNOSTIC = 0xDC;
+ public static final int CLASSID_WIRELESS = 0xE0;
+ public static final int CLASSID_MISC = 0xEF;
+ public static final int CLASSID_APPSPECIFIC = 0xFE;
+ public static final int CLASSID_VENDSPECIFIC = 0xFF;
// Audio Subclass codes
- public static final byte AUDIO_SUBCLASS_UNDEFINED = 0x00;
- public static final byte AUDIO_AUDIOCONTROL = 0x01;
- public static final byte AUDIO_AUDIOSTREAMING = 0x02;
- public static final byte AUDIO_MIDISTREAMING = 0x03;
+ public static final int AUDIO_SUBCLASS_UNDEFINED = 0x00;
+ public static final int AUDIO_AUDIOCONTROL = 0x01;
+ public static final int AUDIO_AUDIOSTREAMING = 0x02;
+ public static final int AUDIO_MIDISTREAMING = 0x03;
// Request IDs
public static final int REQUEST_GET_STATUS = 0x00;
diff --git a/com/android/server/usb/descriptors/UsbDescriptorParser.java b/com/android/server/usb/descriptors/UsbDescriptorParser.java
index ad7bde5c..6c6bd013 100644
--- a/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -15,6 +15,7 @@
*/
package com.android.server.usb.descriptors;
+import android.hardware.usb.UsbDevice;
import android.util.Log;
import java.util.ArrayList;
@@ -25,11 +26,16 @@ import java.util.ArrayList;
*/
public final class UsbDescriptorParser {
private static final String TAG = "UsbDescriptorParser";
+ private static final boolean DEBUG = false;
+
+ private final String mDeviceAddr;
// Descriptor Objects
+ private static final int DESCRIPTORS_ALLOC_SIZE = 128;
private ArrayList<UsbDescriptor> mDescriptors = new ArrayList<UsbDescriptor>();
private UsbDeviceDescriptor mDeviceDescriptor;
+ private UsbConfigDescriptor mCurConfigDescriptor;
private UsbInterfaceDescriptor mCurInterfaceDescriptor;
// The AudioClass spec implemented by the AudioClass Interfaces
@@ -37,7 +43,13 @@ public final class UsbDescriptorParser {
// Obtained from the first AudioClass Header descriptor.
private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0;
- public UsbDescriptorParser() {}
+ public UsbDescriptorParser(String deviceAddr) {
+ mDeviceAddr = deviceAddr;
+ }
+
+ public String getDeviceAddr() {
+ return mDeviceAddr;
+ }
/**
* @return the USB Spec value associated with the Device descriptor for the
@@ -60,6 +72,18 @@ public final class UsbDescriptorParser {
public int getACInterfaceSpec() {
return mACInterfacesSpec;
}
+
+ private class UsbDescriptorsStreamFormatException extends Exception {
+ String mMessage;
+ UsbDescriptorsStreamFormatException(String message) {
+ mMessage = message;
+ }
+
+ public String toString() {
+ return "Descriptor Stream Format Exception: " + mMessage;
+ }
+ }
+
/**
* The probability (as returned by getHeadsetProbability() at which we conclude
* the peripheral is a headset.
@@ -67,7 +91,8 @@ public final class UsbDescriptorParser {
private static final float IN_HEADSET_TRIGGER = 0.75f;
private static final float OUT_HEADSET_TRIGGER = 0.75f;
- private UsbDescriptor allocDescriptor(ByteStream stream) {
+ private UsbDescriptor allocDescriptor(ByteStream stream)
+ throws UsbDescriptorsStreamFormatException {
stream.resetReadCount();
int length = stream.getUnsignedByte();
@@ -83,15 +108,38 @@ public final class UsbDescriptorParser {
break;
case UsbDescriptor.DESCRIPTORTYPE_CONFIG:
- descriptor = new UsbConfigDescriptor(length, type);
+ descriptor = mCurConfigDescriptor = new UsbConfigDescriptor(length, type);
+ if (mDeviceDescriptor != null) {
+ mDeviceDescriptor.addConfigDescriptor(mCurConfigDescriptor);
+ } else {
+ Log.e(TAG, "Config Descriptor found with no associated Device Descriptor!");
+ throw new UsbDescriptorsStreamFormatException(
+ "Config Descriptor found with no associated Device Descriptor!");
+ }
break;
case UsbDescriptor.DESCRIPTORTYPE_INTERFACE:
descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type);
+ if (mCurConfigDescriptor != null) {
+ mCurConfigDescriptor.addInterfaceDescriptor(mCurInterfaceDescriptor);
+ } else {
+ Log.e(TAG, "Interface Descriptor found with no associated Config Descriptor!");
+ throw new UsbDescriptorsStreamFormatException(
+ "Interface Descriptor found with no associated Config Descriptor!");
+ }
break;
case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT:
descriptor = new UsbEndpointDescriptor(length, type);
+ if (mCurInterfaceDescriptor != null) {
+ mCurInterfaceDescriptor.addEndpointDescriptor(
+ (UsbEndpointDescriptor) descriptor);
+ } else {
+ Log.e(TAG,
+ "Endpoint Descriptor found with no associated Interface Descriptor!");
+ throw new UsbDescriptorsStreamFormatException(
+ "Endpoint Descriptor found with no associated Interface Descriptor!");
+ }
break;
/*
@@ -144,8 +192,12 @@ public final class UsbDescriptorParser {
/**
* @hide
*/
- public void parseDescriptors(byte[] descriptors) {
- mDescriptors.clear();
+ public boolean parseDescriptors(byte[] descriptors) {
+ if (DEBUG) {
+ Log.d(TAG, "parseDescriptors() - start");
+ }
+ // This will allow us to (probably) alloc mDescriptors just once.
+ mDescriptors = new ArrayList<UsbDescriptor>(DESCRIPTORS_ALLOC_SIZE);
ByteStream stream = new ByteStream(descriptors);
while (stream.available() > 0) {
@@ -173,21 +225,36 @@ public final class UsbDescriptorParser {
}
}
}
+ if (DEBUG) {
+ Log.d(TAG, "parseDescriptors() - end " + mDescriptors.size() + " descriptors.");
+ }
+ return true;
}
/**
* @hide
*/
- public boolean parseDevice(String deviceAddr) {
- byte[] rawDescriptors = getRawDescriptors(deviceAddr);
- if (rawDescriptors != null) {
- parseDescriptors(rawDescriptors);
- return true;
- }
- return false;
+ public boolean parseDevice() {
+ byte[] rawDescriptors = getRawDescriptors();
+
+ return rawDescriptors != null
+ ? parseDescriptors(rawDescriptors) : false;
+ }
+
+ private byte[] getRawDescriptors() {
+ return getRawDescriptors_native(mDeviceAddr);
+ }
+
+ private native byte[] getRawDescriptors_native(String deviceAddr);
+
+ /**
+ * @hide
+ */
+ public String getDescriptorString(int stringId) {
+ return getDescriptorString_native(mDeviceAddr, stringId);
}
- private native byte[] getRawDescriptors(String deviceAddr);
+ private native String getDescriptorString_native(String deviceAddr, int stringId);
public int getParsingSpec() {
return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0;
@@ -200,6 +267,17 @@ public final class UsbDescriptorParser {
/**
* @hide
*/
+ public UsbDevice toAndroidUsbDevice() {
+ if (mDeviceDescriptor == null) {
+ return null;
+ }
+
+ return mDeviceDescriptor.toAndroid(this);
+ }
+
+ /**
+ * @hide
+ */
public ArrayList<UsbDescriptor> getDescriptors(byte type) {
ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
for (UsbDescriptor descriptor : mDescriptors) {
@@ -213,7 +291,7 @@ public final class UsbDescriptorParser {
/**
* @hide
*/
- public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(byte usbClass) {
+ public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(int usbClass) {
ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
for (UsbDescriptor descriptor : mDescriptors) {
// ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE
@@ -235,7 +313,7 @@ public final class UsbDescriptorParser {
/**
* @hide
*/
- public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, byte subclass) {
+ public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) {
ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
for (UsbDescriptor descriptor : mDescriptors) {
if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) {
@@ -355,8 +433,6 @@ public final class UsbDescriptorParser {
* to count on the peripheral being a headset.
*/
public boolean isInputHeadset() {
- // TEMP
- Log.i(TAG, "---- isInputHeadset() prob:" + (getInputHeadsetProbability() * 100f) + "%");
return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER;
}
@@ -410,8 +486,6 @@ public final class UsbDescriptorParser {
* to count on the peripheral being a headset.
*/
public boolean isOutputHeadset() {
- // TEMP
- Log.i(TAG, "---- isOutputHeadset() prob:" + (getOutputHeadsetProbability() * 100f) + "%");
return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER;
}
diff --git a/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
index d5cb89ea..8e7f0fde 100644
--- a/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
@@ -15,9 +15,14 @@
*/
package com.android.server.usb.descriptors;
+import android.hardware.usb.UsbConfiguration;
+import android.hardware.usb.UsbDevice;
+
import com.android.server.usb.descriptors.report.ReportCanvas;
import com.android.server.usb.descriptors.report.UsbStrings;
+import java.util.ArrayList;
+
/**
* @hide
* A USB Device Descriptor.
@@ -31,9 +36,9 @@ public final class UsbDeviceDescriptor extends UsbDescriptor {
public static final int USBSPEC_2_0 = 0x0200;
private int mSpec; // 2:2 bcdUSB 2 BCD USB Specification Number - BCD
- private byte mDevClass; // 4:1 class code
- private byte mDevSubClass; // 5:1 subclass code
- private byte mProtocol; // 6:1 protocol
+ private int mDevClass; // 4:1 class code
+ private int mDevSubClass; // 5:1 subclass code
+ private int mProtocol; // 6:1 protocol
private byte mPacketSize; // 7:1 Maximum Packet Size for Zero Endpoint.
// Valid Sizes are 8, 16, 32, 64
private int mVendorID; // 8:2 vendor ID
@@ -44,6 +49,9 @@ public final class UsbDeviceDescriptor extends UsbDescriptor {
private byte mSerialNum; // 16:1 Index of Serial Number String Descriptor
private byte mNumConfigs; // 17:1 Number of Possible Configurations
+ private ArrayList<UsbConfigDescriptor> mConfigDescriptors =
+ new ArrayList<UsbConfigDescriptor>();
+
UsbDeviceDescriptor(int length, byte type) {
super(length, type);
mHierarchyLevel = 1;
@@ -53,15 +61,15 @@ public final class UsbDeviceDescriptor extends UsbDescriptor {
return mSpec;
}
- public byte getDevClass() {
+ public int getDevClass() {
return mDevClass;
}
- public byte getDevSubClass() {
+ public int getDevSubClass() {
return mDevSubClass;
}
- public byte getProtocol() {
+ public int getProtocol() {
return mProtocol;
}
@@ -97,12 +105,41 @@ public final class UsbDeviceDescriptor extends UsbDescriptor {
return mNumConfigs;
}
+ void addConfigDescriptor(UsbConfigDescriptor config) {
+ mConfigDescriptors.add(config);
+ }
+
+ /**
+ * @hide
+ */
+ public UsbDevice toAndroid(UsbDescriptorParser parser) {
+ String mfgName = parser.getDescriptorString(mMfgIndex);
+ String prodName = parser.getDescriptorString(mProductIndex);
+
+ // Create version string in "%.%" format
+ String versionString =
+ Integer.toString(mDeviceRelease >> 8) + "." + (mDeviceRelease & 0xFF);
+ String serialStr = parser.getDescriptorString(mSerialNum);
+
+ UsbDevice device = new UsbDevice(parser.getDeviceAddr(), mVendorID, mProductID,
+ mDevClass, mDevSubClass,
+ mProtocol, mfgName, prodName,
+ versionString, serialStr);
+ UsbConfiguration[] configs = new UsbConfiguration[mConfigDescriptors.size()];
+ for (int index = 0; index < mConfigDescriptors.size(); index++) {
+ configs[index] = mConfigDescriptors.get(index).toAndroid(parser);
+ }
+ device.setConfigurations(configs);
+
+ return device;
+ }
+
@Override
public int parseRawDescriptors(ByteStream stream) {
mSpec = stream.unpackUsbShort();
- mDevClass = stream.getByte();
- mDevSubClass = stream.getByte();
- mProtocol = stream.getByte();
+ mDevClass = stream.getUnsignedByte();
+ mDevSubClass = stream.getUnsignedByte();
+ mProtocol = stream.getUnsignedByte();
mPacketSize = stream.getByte();
mVendorID = stream.unpackUsbShort();
mProductID = stream.unpackUsbShort();
@@ -124,9 +161,9 @@ public final class UsbDeviceDescriptor extends UsbDescriptor {
int spec = getSpec();
canvas.writeListItem("Spec: " + ReportCanvas.getBCDString(spec));
- byte devClass = getDevClass();
+ int devClass = getDevClass();
String classStr = UsbStrings.getClassName(devClass);
- byte devSubClass = getDevSubClass();
+ int devSubClass = getDevSubClass();
String subClasStr = UsbStrings.getClassName(devSubClass);
canvas.writeListItem("Class " + devClass + ": " + classStr + " Subclass"
+ devSubClass + ": " + subClasStr);
@@ -134,12 +171,11 @@ public final class UsbDeviceDescriptor extends UsbDescriptor {
+ " Product ID: " + ReportCanvas.getHexString(getProductID())
+ " Product Release: " + ReportCanvas.getBCDString(getDeviceRelease()));
+ UsbDescriptorParser parser = canvas.getParser();
byte mfgIndex = getMfgIndex();
- String manufacturer =
- UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), mfgIndex);
+ String manufacturer = parser.getDescriptorString(mfgIndex);
byte productIndex = getProductIndex();
- String product =
- UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), productIndex);
+ String product = parser.getDescriptorString(productIndex);
canvas.writeListItem("Manufacturer " + mfgIndex + ": " + manufacturer
+ " Product " + productIndex + ": " + product);
diff --git a/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index 6322fbe8..11302380 100644
--- a/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -15,6 +15,8 @@
*/
package com.android.server.usb.descriptors;
+import android.hardware.usb.UsbEndpoint;
+
import com.android.server.usb.descriptors.report.ReportCanvas;
/**
@@ -25,16 +27,16 @@ import com.android.server.usb.descriptors.report.ReportCanvas;
public class UsbEndpointDescriptor extends UsbDescriptor {
private static final String TAG = "UsbEndpointDescriptor";
- public static final byte MASK_ENDPOINT_ADDRESS = 0b0001111;
- public static final byte MASK_ENDPOINT_DIRECTION = (byte) 0b10000000;
- public static final byte DIRECTION_OUTPUT = 0x00;
- public static final byte DIRECTION_INPUT = (byte) 0x80;
+ public static final int MASK_ENDPOINT_ADDRESS = 0b000000000001111;
+ public static final int MASK_ENDPOINT_DIRECTION = (byte) 0b0000000010000000;
+ public static final int DIRECTION_OUTPUT = 0x0000;
+ public static final int DIRECTION_INPUT = (byte) 0x0080;
- public static final byte MASK_ATTRIBS_TRANSTYPE = 0b00000011;
- public static final byte TRANSTYPE_CONTROL = 0x00;
- public static final byte TRANSTYPE_ISO = 0x01;
- public static final byte TRANSTYPE_BULK = 0x02;
- public static final byte TRANSTYPE_INTERRUPT = 0x03;
+ public static final int MASK_ATTRIBS_TRANSTYPE = 0b00000011;
+ public static final int TRANSTYPE_CONTROL = 0x00;
+ public static final int TRANSTYPE_ISO = 0x01;
+ public static final int TRANSTYPE_BULK = 0x02;
+ public static final int TRANSTYPE_INTERRUPT = 0x03;
public static final byte MASK_ATTRIBS_SYNCTYPE = 0b00001100;
public static final byte SYNCTYPE_NONE = 0b00000000;
@@ -42,18 +44,18 @@ public class UsbEndpointDescriptor extends UsbDescriptor {
public static final byte SYNCTYPE_ADAPTSYNC = 0b00001000;
public static final byte SYNCTYPE_RESERVED = 0b00001100;
- public static final byte MASK_ATTRIBS_USEAGE = 0b00110000;
- public static final byte USEAGE_DATA = 0b00000000;
- public static final byte USEAGE_FEEDBACK = 0b00010000;
- public static final byte USEAGE_EXPLICIT = 0b00100000;
- public static final byte USEAGE_RESERVED = 0b00110000;
+ public static final int MASK_ATTRIBS_USEAGE = 0b00110000;
+ public static final int USEAGE_DATA = 0b00000000;
+ public static final int USEAGE_FEEDBACK = 0b00010000;
+ public static final int USEAGE_EXPLICIT = 0b00100000;
+ public static final int USEAGE_RESERVED = 0b00110000;
- private byte mEndpointAddress; // 2:1 Endpoint Address
+ private int mEndpointAddress; // 2:1 Endpoint Address
// Bits 0..3b Endpoint Number.
// Bits 4..6b Reserved. Set to Zero
// Bits 7 Direction 0 = Out, 1 = In
// (Ignored for Control Endpoints)
- private byte mAttributes; // 3:1 Various flags
+ private int mAttributes; // 3:1 Various flags
// Bits 0..1 Transfer Type:
// 00 = Control, 01 = Isochronous, 10 = Bulk, 11 = Interrupt
// Bits 2..7 are reserved. If Isochronous endpoint,
@@ -69,7 +71,7 @@ public class UsbEndpointDescriptor extends UsbDescriptor {
// 11: Reserved
private int mPacketSize; // 4:2 Maximum Packet Size this endpoint is capable of
// sending or receiving
- private byte mInterval; // 6:1 Interval for polling endpoint data transfers. Value in
+ private int mInterval; // 6:1 Interval for polling endpoint data transfers. Value in
// frame counts.
// Ignored for Bulk & Control Endpoints. Isochronous must equal
// 1 and field may range from 1 to 255 for interrupt endpoints.
@@ -81,11 +83,11 @@ public class UsbEndpointDescriptor extends UsbDescriptor {
mHierarchyLevel = 4;
}
- public byte getEndpointAddress() {
+ public int getEndpointAddress() {
return mEndpointAddress;
}
- public byte getAttributes() {
+ public int getAttributes() {
return mAttributes;
}
@@ -93,7 +95,7 @@ public class UsbEndpointDescriptor extends UsbDescriptor {
return mPacketSize;
}
- public byte getInterval() {
+ public int getInterval() {
return mInterval;
}
@@ -105,12 +107,16 @@ public class UsbEndpointDescriptor extends UsbDescriptor {
return mSyncAddress;
}
+ /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) {
+ return new UsbEndpoint(mEndpointAddress, mAttributes, mPacketSize, mInterval);
+ }
+
@Override
public int parseRawDescriptors(ByteStream stream) {
- mEndpointAddress = stream.getByte();
- mAttributes = stream.getByte();
+ mEndpointAddress = stream.getUnsignedByte();
+ mAttributes = stream.getUnsignedByte();
mPacketSize = stream.unpackUsbShort();
- mInterval = stream.getByte();
+ mInterval = stream.getUnsignedByte();
if (mLength == 9) {
mRefresh = stream.getByte();
mSyncAddress = stream.getByte();
@@ -124,13 +130,13 @@ public class UsbEndpointDescriptor extends UsbDescriptor {
canvas.openList();
- byte address = getEndpointAddress();
+ int address = getEndpointAddress();
canvas.writeListItem("Address: "
+ ReportCanvas.getHexString(address & UsbEndpointDescriptor.MASK_ENDPOINT_ADDRESS)
+ ((address & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION)
== UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]"));
- byte attributes = getAttributes();
+ int attributes = getAttributes();
canvas.openListItem();
canvas.write("Attributes: " + ReportCanvas.getHexString(attributes) + " ");
switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_TRANSTYPE) {
diff --git a/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
index 4eef6caf..d87b1afb 100644
--- a/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -15,9 +15,14 @@
*/
package com.android.server.usb.descriptors;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+
import com.android.server.usb.descriptors.report.ReportCanvas;
import com.android.server.usb.descriptors.report.UsbStrings;
+import java.util.ArrayList;
+
/**
* @hide
* A common super-class for all USB Interface Descritor subtypes.
@@ -26,14 +31,17 @@ import com.android.server.usb.descriptors.report.UsbStrings;
public class UsbInterfaceDescriptor extends UsbDescriptor {
private static final String TAG = "UsbInterfaceDescriptor";
- protected byte mInterfaceNumber; // 2:1 Number of Interface
+ protected int mInterfaceNumber; // 2:1 Number of Interface
protected byte mAlternateSetting; // 3:1 Value used to select alternative setting
protected byte mNumEndpoints; // 4:1 Number of Endpoints used for this interface
- protected byte mUsbClass; // 5:1 Class Code
- protected byte mUsbSubclass; // 6:1 Subclass Code
- protected byte mProtocol; // 7:1 Protocol Code
+ protected int mUsbClass; // 5:1 Class Code
+ protected int mUsbSubclass; // 6:1 Subclass Code
+ protected int mProtocol; // 7:1 Protocol Code
protected byte mDescrIndex; // 8:1 Index of String Descriptor Describing this interface
+ private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors =
+ new ArrayList<UsbEndpointDescriptor>();
+
UsbInterfaceDescriptor(int length, byte type) {
super(length, type);
mHierarchyLevel = 3;
@@ -41,18 +49,18 @@ public class UsbInterfaceDescriptor extends UsbDescriptor {
@Override
public int parseRawDescriptors(ByteStream stream) {
- mInterfaceNumber = stream.getByte();
+ mInterfaceNumber = stream.getUnsignedByte();
mAlternateSetting = stream.getByte();
mNumEndpoints = stream.getByte();
- mUsbClass = stream.getByte();
- mUsbSubclass = stream.getByte();
- mProtocol = stream.getByte();
+ mUsbClass = stream.getUnsignedByte();
+ mUsbSubclass = stream.getUnsignedByte();
+ mProtocol = stream.getUnsignedByte();
mDescrIndex = stream.getByte();
return mLength;
}
- public byte getInterfaceNumber() {
+ public int getInterfaceNumber() {
return mInterfaceNumber;
}
@@ -64,15 +72,15 @@ public class UsbInterfaceDescriptor extends UsbDescriptor {
return mNumEndpoints;
}
- public byte getUsbClass() {
+ public int getUsbClass() {
return mUsbClass;
}
- public byte getUsbSubclass() {
+ public int getUsbSubclass() {
return mUsbSubclass;
}
- public byte getProtocol() {
+ public int getProtocol() {
return mProtocol;
}
@@ -80,13 +88,29 @@ public class UsbInterfaceDescriptor extends UsbDescriptor {
return mDescrIndex;
}
+ void addEndpointDescriptor(UsbEndpointDescriptor endpoint) {
+ mEndpointDescriptors.add(endpoint);
+ }
+
+ UsbInterface toAndroid(UsbDescriptorParser parser) {
+ String name = parser.getDescriptorString(mDescrIndex);
+ UsbInterface ntrface = new UsbInterface(
+ mInterfaceNumber, mAlternateSetting, name, mUsbClass, mUsbSubclass, mProtocol);
+ UsbEndpoint[] endpoints = new UsbEndpoint[mEndpointDescriptors.size()];
+ for (int index = 0; index < mEndpointDescriptors.size(); index++) {
+ endpoints[index] = mEndpointDescriptors.get(index).toAndroid(parser);
+ }
+ ntrface.setEndpoints(endpoints);
+ return ntrface;
+ }
+
@Override
public void report(ReportCanvas canvas) {
super.report(canvas);
- byte usbClass = getUsbClass();
- byte usbSubclass = getUsbSubclass();
- byte protocol = getProtocol();
+ int usbClass = getUsbClass();
+ int usbSubclass = getUsbSubclass();
+ int protocol = getProtocol();
String className = UsbStrings.getClassName(usbClass);
String subclassName = "";
if (usbClass == UsbDescriptor.CLASSID_AUDIO) {
diff --git a/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/com/android/server/usb/descriptors/UsbMSMidiHeader.java
index 85a3e680..d0ca6db8 100644
--- a/com/android/server/usb/descriptors/UsbMSMidiHeader.java
+++ b/com/android/server/usb/descriptors/UsbMSMidiHeader.java
@@ -25,7 +25,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas;
public final class UsbMSMidiHeader extends UsbACInterface {
private static final String TAG = "UsbMSMidiHeader";
- public UsbMSMidiHeader(int length, byte type, byte subtype, byte subclass) {
+ public UsbMSMidiHeader(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbMSMidiInputJack.java b/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
index 1d5cbf2b..7df7cfc6 100644
--- a/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
+++ b/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
@@ -25,7 +25,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas;
public final class UsbMSMidiInputJack extends UsbACInterface {
private static final String TAG = "UsbMSMidiInputJack";
- UsbMSMidiInputJack(int length, byte type, byte subtype, byte subclass) {
+ UsbMSMidiInputJack(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java b/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
index 9f50240a..1879ac09 100644
--- a/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
+++ b/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
@@ -25,7 +25,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas;
public final class UsbMSMidiOutputJack extends UsbACInterface {
private static final String TAG = "UsbMSMidiOutputJack";
- public UsbMSMidiOutputJack(int length, byte type, byte subtype, byte subclass) {
+ public UsbMSMidiOutputJack(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/report/HTMLReportCanvas.java b/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
index 99ebccad..adfc5143 100644
--- a/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
+++ b/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
@@ -15,7 +15,7 @@
*/
package com.android.server.usb.descriptors.report;
-import android.hardware.usb.UsbDeviceConnection;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
/**
* @hide
@@ -32,8 +32,8 @@ public final class HTMLReportCanvas extends ReportCanvas {
* from the USB device.
* @param stringBuilder Generated output gets written into this object.
*/
- public HTMLReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) {
- super(connection);
+ public HTMLReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) {
+ super(parser);
mStringBuilder = stringBuilder;
}
diff --git a/com/android/server/usb/descriptors/report/ReportCanvas.java b/com/android/server/usb/descriptors/report/ReportCanvas.java
index 9e0adf55..c34dc988 100644
--- a/com/android/server/usb/descriptors/report/ReportCanvas.java
+++ b/com/android/server/usb/descriptors/report/ReportCanvas.java
@@ -15,7 +15,7 @@
*/
package com.android.server.usb.descriptors.report;
-import android.hardware.usb.UsbDeviceConnection;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
/**
* @hide
@@ -24,22 +24,19 @@ import android.hardware.usb.UsbDeviceConnection;
public abstract class ReportCanvas {
private static final String TAG = "ReportCanvas";
- private final UsbDeviceConnection mConnection;
+ private final UsbDescriptorParser mParser;
/**
* Constructor.
* @param connection The USB connection object used to retrieve strings
* from the USB device.
*/
- public ReportCanvas(UsbDeviceConnection connection) {
- mConnection = connection;
+ public ReportCanvas(UsbDescriptorParser parser) {
+ mParser = parser;
}
- /**
- * @returns the UsbDeviceConnection member (mConnection).
- */
- public UsbDeviceConnection getConnection() {
- return mConnection;
+ public UsbDescriptorParser getParser() {
+ return mParser;
}
/**
diff --git a/com/android/server/usb/descriptors/report/TextReportCanvas.java b/com/android/server/usb/descriptors/report/TextReportCanvas.java
index a43569d4..1e19ea14 100644
--- a/com/android/server/usb/descriptors/report/TextReportCanvas.java
+++ b/com/android/server/usb/descriptors/report/TextReportCanvas.java
@@ -15,7 +15,7 @@
*/
package com.android.server.usb.descriptors.report;
-import android.hardware.usb.UsbDeviceConnection;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
/**
* @hide
@@ -34,8 +34,8 @@ public final class TextReportCanvas extends ReportCanvas {
* from the USB device.
* @param stringBuilder Generated output gets written into this object.
*/
- public TextReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) {
- super(connection);
+ public TextReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) {
+ super(parser);
mStringBuilder = stringBuilder;
}
diff --git a/com/android/server/usb/descriptors/report/UsbStrings.java b/com/android/server/usb/descriptors/report/UsbStrings.java
index 64ecebc2..fb4576a6 100644
--- a/com/android/server/usb/descriptors/report/UsbStrings.java
+++ b/com/android/server/usb/descriptors/report/UsbStrings.java
@@ -32,8 +32,8 @@ public final class UsbStrings {
private static HashMap<Byte, String> sDescriptorNames;
private static HashMap<Byte, String> sACControlInterfaceNames;
private static HashMap<Byte, String> sACStreamingInterfaceNames;
- private static HashMap<Byte, String> sClassNames;
- private static HashMap<Byte, String> sAudioSubclassNames;
+ private static HashMap<Integer, String> sClassNames;
+ private static HashMap<Integer, String> sAudioSubclassNames;
private static HashMap<Integer, String> sAudioEncodingNames;
private static HashMap<Integer, String> sTerminalNames;
private static HashMap<Integer, String> sFormatNames;
@@ -92,7 +92,7 @@ public final class UsbStrings {
}
private static void initClassNames() {
- sClassNames = new HashMap<Byte, String>();
+ sClassNames = new HashMap<Integer, String>();
sClassNames.put(UsbDescriptor.CLASSID_DEVICE, "Device");
sClassNames.put(UsbDescriptor.CLASSID_AUDIO, "Audio");
sClassNames.put(UsbDescriptor.CLASSID_COM, "Communications");
@@ -118,7 +118,7 @@ public final class UsbStrings {
}
private static void initAudioSubclassNames() {
- sAudioSubclassNames = new HashMap<Byte, String>();
+ sAudioSubclassNames = new HashMap<Integer, String>();
sAudioSubclassNames.put(UsbDescriptor.AUDIO_SUBCLASS_UNDEFINED, "Undefinded");
sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOCONTROL, "Audio Control");
sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOSTREAMING, "Audio Streaming");
@@ -300,7 +300,7 @@ public final class UsbStrings {
/**
* Retrieves the name for the specified USB class ID.
*/
- public static String getClassName(byte classID) {
+ public static String getClassName(int classID) {
String name = sClassNames.get(classID);
int iClassID = classID & 0xFF;
return name != null
@@ -312,7 +312,7 @@ public final class UsbStrings {
/**
* Retrieves the name for the specified USB audio subclass ID.
*/
- public static String getAudioSubclassName(byte subClassID) {
+ public static String getAudioSubclassName(int subClassID) {
String name = sAudioSubclassNames.get(subClassID);
int iSubclassID = subClassID & 0xFF;
return name != null
@@ -335,7 +335,7 @@ public final class UsbStrings {
/**
* Retrieves the name for the specified USB audio interface subclass ID.
*/
- public static String getACInterfaceSubclassName(byte subClassID) {
+ public static String getACInterfaceSubclassName(int subClassID) {
return subClassID == UsbDescriptor.AUDIO_AUDIOCONTROL ? "AC Control" : "AC Streaming";
}
}
diff --git a/com/android/server/utils/AppInstallerUtil.java b/com/android/server/utils/AppInstallerUtil.java
new file mode 100644
index 00000000..af7ff41f
--- /dev/null
+++ b/com/android/server/utils/AppInstallerUtil.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.util.Log;
+
+public class AppInstallerUtil {
+ private static final String LOG_TAG = "AppInstallerUtil";
+
+ private static Intent resolveIntent(Context context, Intent i) {
+ ResolveInfo result = context.getPackageManager().resolveActivity(i, 0);
+ return result != null ? new Intent(i.getAction())
+ .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
+ }
+
+ /**
+ * Returns the package name of the app which installed a given packageName, if available.
+ */
+ public static String getInstallerPackageName(Context context, String packageName) {
+ String installerPackageName = null;
+ try {
+ installerPackageName =
+ context.getPackageManager().getInstallerPackageName(packageName);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOG_TAG, "Exception while retrieving the package installer of " + packageName, e);
+ }
+ if (installerPackageName == null) {
+ return null;
+ }
+ return installerPackageName;
+ }
+
+ /**
+ * Returns an intent to launcher the installer for a given package name.
+ */
+ public static Intent createIntent(Context context, String installerPackageName,
+ String packageName) {
+ Intent intent = new Intent(Intent.ACTION_SHOW_APP_INFO).setPackage(installerPackageName);
+ final Intent result = resolveIntent(context, intent);
+ if (result != null) {
+ result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ return result;
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method that looks up the installerPackageName.
+ */
+ public static Intent createIntent(Context context, String packageName) {
+ String installerPackageName = getInstallerPackageName(context, packageName);
+ return createIntent(context, installerPackageName, packageName);
+ }
+}
diff --git a/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 44e5314f..2d93da9f 100644
--- a/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -427,8 +427,11 @@ public class VoiceInteractionManagerService extends SystemService {
if (hasComponent) {
mShortcutServiceInternal.setShortcutHostPackage(TAG,
serviceComponent.getPackageName(), mCurUser);
+ mAmInternal.setAllowAppSwitches(TAG,
+ serviceInfo.applicationInfo.uid, mCurUser);
} else {
mShortcutServiceInternal.setShortcutHostPackage(TAG, null, mCurUser);
+ mAmInternal.setAllowAppSwitches(TAG, -1, mCurUser);
}
}
diff --git a/com/android/server/vr/Vr2dDisplay.java b/com/android/server/vr/Vr2dDisplay.java
index 95d03d4b..7866e7d4 100644
--- a/com/android/server/vr/Vr2dDisplay.java
+++ b/com/android/server/vr/Vr2dDisplay.java
@@ -24,9 +24,8 @@ import android.service.vr.IPersistentVrStateCallbacks;
import android.service.vr.IVrManager;
import android.util.Log;
import android.view.Surface;
-import android.view.WindowManagerInternal;
-import com.android.server.vr.VrManagerService;
+import com.android.server.wm.WindowManagerInternal;
/**
* Creates a 2D Virtual Display while VR Mode is enabled. This display will be used to run and
diff --git a/com/android/server/vr/VrManagerService.java b/com/android/server/vr/VrManagerService.java
index 5493207d..1f4e64e8 100644
--- a/com/android/server/vr/VrManagerService.java
+++ b/com/android/server/vr/VrManagerService.java
@@ -58,7 +58,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
import com.android.internal.R;
import com.android.internal.util.DumpUtils;
diff --git a/com/android/server/wifi/HalDeviceManager.java b/com/android/server/wifi/HalDeviceManager.java
index 0cc735af..ffc7113f 100644
--- a/com/android/server/wifi/HalDeviceManager.java
+++ b/com/android/server/wifi/HalDeviceManager.java
@@ -16,6 +16,8 @@
package com.android.server.wifi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.hardware.wifi.V1_0.IWifi;
import android.hardware.wifi.V1_0.IWifiApIface;
import android.hardware.wifi.V1_0.IWifiChip;
@@ -34,7 +36,6 @@ import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
import android.os.Handler;
import android.os.HwRemoteBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.util.LongSparseArray;
@@ -102,13 +103,14 @@ public class HalDeviceManager {
* single copy kept.
*
* @param listener ManagerStatusListener listener object.
- * @param handler Handler on which to dispatch listener. Null implies a new Handler based on
- * the current looper.
+ * @param handler Handler on which to dispatch listener. Null implies the listener will be
+ * invoked synchronously from the context of the client which triggered the
+ * state change.
*/
- public void registerStatusListener(ManagerStatusListener listener, Handler handler) {
+ public void registerStatusListener(@NonNull ManagerStatusListener listener,
+ @Nullable Handler handler) {
synchronized (mLock) {
- if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener,
- handler == null ? new Handler(Looper.myLooper()) : handler))) {
+ if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener, handler))) {
Log.w(TAG, "registerStatusListener: duplicate registration ignored");
}
}
@@ -197,36 +199,37 @@ public class HalDeviceManager {
* @param destroyedListener Optional (nullable) listener to call when the allocated interface
* is removed. Will only be registered and used if an interface is
* created successfully.
- * @param handler The Handler on which to dispatch the listener. A null implies a new Handler
- * based on the current looper.
+ * @param handler Handler on which to dispatch listener. Null implies the listener will be
+ * invoked synchronously from the context of the client which triggered the
+ * iface destruction.
* @return A newly created interface - or null if the interface could not be created.
*/
- public IWifiStaIface createStaIface(InterfaceDestroyedListener destroyedListener,
- Handler handler) {
+ public IWifiStaIface createStaIface(@Nullable InterfaceDestroyedListener destroyedListener,
+ @Nullable Handler handler) {
return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, handler);
}
/**
* Create AP interface if possible (see createStaIface doc).
*/
- public IWifiApIface createApIface(InterfaceDestroyedListener destroyedListener,
- Handler handler) {
+ public IWifiApIface createApIface(@Nullable InterfaceDestroyedListener destroyedListener,
+ @Nullable Handler handler) {
return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, handler);
}
/**
* Create P2P interface if possible (see createStaIface doc).
*/
- public IWifiP2pIface createP2pIface(InterfaceDestroyedListener destroyedListener,
- Handler handler) {
+ public IWifiP2pIface createP2pIface(@Nullable InterfaceDestroyedListener destroyedListener,
+ @Nullable Handler handler) {
return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, handler);
}
/**
* Create NAN interface if possible (see createStaIface doc).
*/
- public IWifiNanIface createNanIface(InterfaceDestroyedListener destroyedListener,
- Handler handler) {
+ public IWifiNanIface createNanIface(@Nullable InterfaceDestroyedListener destroyedListener,
+ @Nullable Handler handler) {
return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, handler);
}
@@ -268,11 +271,16 @@ public class HalDeviceManager {
* and false on failure. This listener is in addition to the one registered when the interface
* was created - allowing non-creators to monitor interface status.
*
- * Listener called-back on the specified handler - or on the current looper if a null is passed.
+ * @param destroyedListener Listener to call when the allocated interface is removed.
+ * Will only be registered and used if an interface is created
+ * successfully.
+ * @param handler Handler on which to dispatch listener. Null implies the listener will be
+ * invoked synchronously from the context of the client which triggered the
+ * iface destruction.
*/
public boolean registerDestroyedListener(IWifiIface iface,
- InterfaceDestroyedListener destroyedListener,
- Handler handler) {
+ @NonNull InterfaceDestroyedListener destroyedListener,
+ @Nullable Handler handler) {
String name = getName(iface);
if (DBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + name);
@@ -284,7 +292,7 @@ public class HalDeviceManager {
}
return cacheEntry.destroyedListeners.add(
- new InterfaceDestroyedListenerProxy(destroyedListener, handler));
+ new InterfaceDestroyedListenerProxy(name, destroyedListener, handler));
}
}
@@ -303,11 +311,12 @@ public class HalDeviceManager {
* @param ifaceType The interface type (IfaceType) to be monitored.
* @param listener Listener to call when an interface of the requested
* type could be created
- * @param handler The Handler on which to dispatch the listener. A null implies a new Handler
- * on the current looper.
+ * @param handler Handler on which to dispatch listener. Null implies the listener will be
+ * invoked synchronously from the context of the client which triggered the
+ * mode change.
*/
public void registerInterfaceAvailableForRequestListener(int ifaceType,
- InterfaceAvailableForRequestListener listener, Handler handler) {
+ @NonNull InterfaceAvailableForRequestListener listener, @Nullable Handler handler) {
if (DBG) Log.d(TAG, "registerInterfaceAvailableForRequestListener: ifaceType=" + ifaceType);
synchronized (mLock) {
@@ -382,8 +391,10 @@ public class HalDeviceManager {
*
* Can be registered when the interface is requested with createXxxIface() - will
* only be valid if the interface creation was successful - i.e. a non-null was returned.
+ *
+ * @param ifaceName Name of the interface that was destroyed.
*/
- void onDestroyed();
+ void onDestroyed(@NonNull String ifaceName);
}
/**
@@ -1212,9 +1223,8 @@ public class HalDeviceManager {
private class ManagerStatusListenerProxy extends
ListenerProxy<ManagerStatusListener> {
- ManagerStatusListenerProxy(ManagerStatusListener statusListener,
- Handler handler) {
- super(statusListener, handler, true, "ManagerStatusListenerProxy");
+ ManagerStatusListenerProxy(ManagerStatusListener statusListener, Handler handler) {
+ super(statusListener, handler, "ManagerStatusListenerProxy");
}
@Override
@@ -1346,7 +1356,8 @@ public class HalDeviceManager {
cacheEntry.type = ifaceType;
if (destroyedListener != null) {
cacheEntry.destroyedListeners.add(
- new InterfaceDestroyedListenerProxy(destroyedListener, handler));
+ new InterfaceDestroyedListenerProxy(
+ cacheEntry.name, destroyedListener, handler));
}
cacheEntry.creationTime = mClock.getUptimeSinceBootMillis();
@@ -1898,11 +1909,8 @@ public class HalDeviceManager {
}
private abstract class ListenerProxy<LISTENER> {
- private static final int LISTENER_TRIGGERED = 0;
-
protected LISTENER mListener;
private Handler mHandler;
- private boolean mFrontOfQueue;
// override equals & hash to make sure that the container HashSet is unique with respect to
// the contained listener
@@ -1917,37 +1925,36 @@ public class HalDeviceManager {
}
void trigger() {
- if (mFrontOfQueue) {
- mHandler.postAtFrontOfQueue(() -> {
- action();
- });
- } else {
+ if (mHandler != null) {
mHandler.post(() -> {
action();
});
+ } else {
+ action();
}
}
protected abstract void action();
- ListenerProxy(LISTENER listener, Handler handler, boolean frontOfQueue, String tag) {
+ ListenerProxy(LISTENER listener, Handler handler, String tag) {
mListener = listener;
mHandler = handler;
- mFrontOfQueue = frontOfQueue;
}
}
private class InterfaceDestroyedListenerProxy extends
ListenerProxy<InterfaceDestroyedListener> {
- InterfaceDestroyedListenerProxy(InterfaceDestroyedListener destroyedListener,
- Handler handler) {
- super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler,
- true, "InterfaceDestroyedListenerProxy");
+ private final String mIfaceName;
+ InterfaceDestroyedListenerProxy(@NonNull String ifaceName,
+ InterfaceDestroyedListener destroyedListener,
+ Handler handler) {
+ super(destroyedListener, handler, "InterfaceDestroyedListenerProxy");
+ mIfaceName = ifaceName;
}
@Override
protected void action() {
- mListener.onDestroyed();
+ mListener.onDestroyed(mIfaceName);
}
}
@@ -1955,8 +1962,7 @@ public class HalDeviceManager {
ListenerProxy<InterfaceAvailableForRequestListener> {
InterfaceAvailableForRequestListenerProxy(
InterfaceAvailableForRequestListener destroyedListener, Handler handler) {
- super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler,
- false, "InterfaceAvailableForRequestListenerProxy");
+ super(destroyedListener, handler, "InterfaceAvailableForRequestListenerProxy");
}
@Override
diff --git a/com/android/server/wifi/SoftApManager.java b/com/android/server/wifi/SoftApManager.java
index e045c396..cec9887c 100644
--- a/com/android/server/wifi/SoftApManager.java
+++ b/com/android/server/wifi/SoftApManager.java
@@ -25,9 +25,7 @@ import android.content.Context;
import android.content.Intent;
import android.net.InterfaceConfiguration;
import android.net.wifi.IApInterface;
-import android.net.wifi.IApInterfaceEventCallback;
import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiManager;
import android.os.INetworkManagementService;
import android.os.Looper;
@@ -41,9 +39,9 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.server.net.BaseNetworkObserver;
+import com.android.server.wifi.WifiNative.SoftApListener;
import com.android.server.wifi.util.ApConfigUtil;
-import java.nio.charset.StandardCharsets;
import java.util.Locale;
/**
@@ -76,16 +74,14 @@ public class SoftApManager implements ActiveModeManager {
private int mNumAssociatedStations = 0;
- /**
- * Listener for AP Interface events.
- */
- public class ApInterfaceListener extends IApInterfaceEventCallback.Stub {
+ private final SoftApListener mSoftApListener = new SoftApListener() {
@Override
public void onNumAssociatedStationsChanged(int numStations) {
mStateMachine.sendMessage(
SoftApStateMachine.CMD_NUM_ASSOCIATED_STATIONS_CHANGED, numStations);
}
- }
+ };
+
/**
* Listener for soft AP state changes.
@@ -227,71 +223,24 @@ public class SoftApManager implements ActiveModeManager {
return ERROR_GENERIC;
}
}
-
- int encryptionType = getIApInterfaceEncryptionType(localConfig);
-
if (localConfig.hiddenSSID) {
Log.d(TAG, "SoftAP is a hidden network");
}
-
- try {
- // Note that localConfig.SSID is intended to be either a hex string or "double quoted".
- // However, it seems that whatever is handing us these configurations does not obey
- // this convention.
- boolean success = mApInterface.writeHostapdConfig(
- localConfig.SSID.getBytes(StandardCharsets.UTF_8), localConfig.hiddenSSID,
- localConfig.apChannel, encryptionType,
- (localConfig.preSharedKey != null)
- ? localConfig.preSharedKey.getBytes(StandardCharsets.UTF_8)
- : new byte[0]);
- if (!success) {
- Log.e(TAG, "Failed to write hostapd configuration");
- return ERROR_GENERIC;
- }
-
- success = mApInterface.startHostapd(new ApInterfaceListener());
- if (!success) {
- Log.e(TAG, "Failed to start hostapd.");
- return ERROR_GENERIC;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Exception in starting soft AP: " + e);
+ if (!mWifiNative.startSoftAp(localConfig, mSoftApListener)) {
+ Log.e(TAG, "Soft AP start failed");
+ return ERROR_GENERIC;
}
-
Log.d(TAG, "Soft AP is started");
return SUCCESS;
}
- private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) {
- int encryptionType;
- switch (localConfig.getAuthType()) {
- case KeyMgmt.NONE:
- encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
- break;
- case KeyMgmt.WPA_PSK:
- encryptionType = IApInterface.ENCRYPTION_TYPE_WPA;
- break;
- case KeyMgmt.WPA2_PSK:
- encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2;
- break;
- default:
- // We really shouldn't default to None, but this was how NetworkManagementService
- // used to do this.
- encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
- break;
- }
- return encryptionType;
- }
-
/**
* Teardown soft AP.
*/
private void stopSoftAp() {
- try {
- mApInterface.stopHostapd();
- } catch (RemoteException e) {
- Log.e(TAG, "Exception in stopping soft AP: " + e);
+ if (!mWifiNative.stopSoftAp()) {
+ Log.d(TAG, "Soft AP stop failed");
return;
}
Log.d(TAG, "Soft AP is stopped");
diff --git a/com/android/server/wifi/WakeupController.java b/com/android/server/wifi/WakeupController.java
new file mode 100644
index 00000000..a3c095ac
--- /dev/null
+++ b/com/android/server/wifi/WakeupController.java
@@ -0,0 +1,74 @@
+/*
+ * 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.wifi;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * WakeupController is responsible managing Auto Wifi.
+ *
+ * <p>It determines if and when to re-enable wifi after it has been turned off by the user.
+ */
+public class WakeupController {
+
+ // TODO(b/69624403) propagate this to Settings
+ private static final boolean USE_PLATFORM_WIFI_WAKE = false;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final FrameworkFacade mFrameworkFacade;
+ private final ContentObserver mContentObserver;
+
+ /** Whether this feature is enabled in Settings. */
+ private boolean mWifiWakeupEnabled;
+
+ public WakeupController(
+ Context context,
+ Looper looper,
+ FrameworkFacade frameworkFacade) {
+ mContext = context;
+ mHandler = new Handler(looper);
+ mFrameworkFacade = frameworkFacade;
+ mContentObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mWifiWakeupEnabled = mFrameworkFacade.getIntegerSetting(
+ mContext, Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1;
+ }
+ };
+ mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor(
+ Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver);
+ mContentObserver.onChange(false /* selfChange */);
+ }
+
+ /**
+ * Whether the feature is enabled in settings.
+ *
+ * <p>Note: This method is only used to determine whether or not to actually enable wifi. All
+ * other aspects of the WakeupController lifecycle operate normally irrespective of this.
+ */
+ @VisibleForTesting
+ boolean isEnabled() {
+ return mWifiWakeupEnabled;
+ }
+}
diff --git a/com/android/server/wifi/WifiInjector.java b/com/android/server/wifi/WifiInjector.java
index cc559341..f14a57f6 100644
--- a/com/android/server/wifi/WifiInjector.java
+++ b/com/android/server/wifi/WifiInjector.java
@@ -122,6 +122,7 @@ public class WifiInjector {
private final WifiStateTracker mWifiStateTracker;
private final Runtime mJavaRuntime;
private final SelfRecovery mSelfRecovery;
+ private final WakeupController mWakeupController;
private final boolean mUseRealLogger;
@@ -230,6 +231,8 @@ public class WifiInjector {
mWifiConfigManager, mWifiConfigStore, mWifiStateMachine,
new OpenNetworkRecommender(),
new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade));
+ mWakeupController = new WakeupController(mContext,
+ mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade);
mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService());
mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore,
mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade);
@@ -349,6 +352,10 @@ public class WifiInjector {
return mPasspointManager;
}
+ public WakeupController getWakeupController() {
+ return mWakeupController;
+ }
+
public TelephonyManager makeTelephonyManager() {
// may not be available when WiFi starts
return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java
index cda1cf6f..cf80d223 100644
--- a/com/android/server/wifi/WifiNative.java
+++ b/com/android/server/wifi/WifiNative.java
@@ -273,6 +273,36 @@ public class WifiNative {
return mWificondControl.stopPnoScan();
}
+ /**
+ * Callbacks for SoftAp interface.
+ */
+ public interface SoftApListener {
+ /**
+ * Invoked when the number of associated stations changes.
+ */
+ void onNumAssociatedStationsChanged(int numStations);
+ }
+
+ /**
+ * Start Soft AP operation using the provided configuration.
+ *
+ * @param config Configuration to use for the soft ap created.
+ * @param listener Callback for AP events.
+ * @return true on success, false otherwise.
+ */
+ public boolean startSoftAp(WifiConfiguration config, SoftApListener listener) {
+ return mWificondControl.startSoftAp(config, listener);
+ }
+
+ /**
+ * Stop the ongoing Soft AP operation.
+ *
+ * @return true on success, false otherwise.
+ */
+ public boolean stopSoftAp() {
+ return mWificondControl.stopSoftAp();
+ }
+
/********************************************************
* Supplicant operations
********************************************************/
diff --git a/com/android/server/wifi/WifiServiceImpl.java b/com/android/server/wifi/WifiServiceImpl.java
index e86bd541..8db180f1 100644
--- a/com/android/server/wifi/WifiServiceImpl.java
+++ b/com/android/server/wifi/WifiServiceImpl.java
@@ -16,7 +16,6 @@
package com.android.server.wifi;
-import static android.app.AppOpsManager.MODE_IGNORED;
import static android.net.wifi.WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_FAILURE_REASON;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
@@ -43,7 +42,6 @@ import static com.android.server.wifi.WifiController.CMD_USER_PRESENT;
import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
import android.Manifest;
-import android.annotation.CheckResult;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.AppOpsManager;
@@ -75,6 +73,7 @@ import android.net.wifi.WifiLinkLayerStats;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
import android.net.wifi.WifiScanner;
+import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.os.AsyncTask;
@@ -143,11 +142,6 @@ public class WifiServiceImpl extends IWifiManager.Stub {
private static final boolean DBG = true;
private static final boolean VDBG = false;
- // Dumpsys argument to enable/disable disconnect on IP reachability failures.
- private static final String DUMP_ARG_SET_IPREACH_DISCONNECT = "set-ipreach-disconnect";
- private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_ENABLED = "enabled";
- private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_DISABLED = "disabled";
-
// Default scan background throttling interval if not overriden in settings
private static final long DEFAULT_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000;
@@ -591,9 +585,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
*/
@Override
public void startScan(ScanSettings settings, WorkSource workSource, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return;
- }
+ enforceChangePermission();
mLog.info("startScan uid=%").c(Binder.getCallingUid()).flush();
// Check and throttle background apps for wifi scan.
@@ -737,21 +729,9 @@ public class WifiServiceImpl extends IWifiManager.Stub {
"WifiService");
}
- /**
- * Checks whether the caller can change the wifi state.
- * Possible results:
- * 1. Operation is allowed. No exception thrown, and AppOpsManager.MODE_ALLOWED returned.
- * 2. Operation is not allowed, and caller must be told about this. SecurityException is thrown.
- * 3. Operation is not allowed, and caller must not be told about this (i.e. must silently
- * ignore the operation). No exception is thrown, and AppOpsManager.MODE_IGNORED returned.
- */
- @CheckResult
- private int enforceChangePermission(String callingPackage) {
+ private void enforceChangePermission() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
"WifiService");
-
- return mAppOps.noteOp(AppOpsManager.OP_CHANGE_WIFI_STATE, Binder.getCallingUid(),
- callingPackage);
}
private void enforceLocationHardwarePermission() {
@@ -795,10 +775,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
@Override
public synchronized boolean setWifiEnabled(String packageName, boolean enable)
throws RemoteException {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
-
+ enforceChangePermission();
Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid() + ", package=" + packageName);
mLog.info("setWifiEnabled package=% uid=% enable=%").c(packageName)
@@ -1192,9 +1169,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return LocalOnlyHotspotCallback.ERROR_GENERIC;
- }
+ enforceChangePermission();
enforceLocationPermission(packageName, uid);
// also need to verify that Locations services are enabled.
if (mSettingsStore.getLocationModeSetting(mContext) == Settings.Secure.LOCATION_MODE_OFF) {
@@ -1266,12 +1241,9 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* Hotspot.
*/
@Override
- public void stopLocalOnlyHotspot(String packageName) {
+ public void stopLocalOnlyHotspot() {
// first check if the caller has permission to stop a local only hotspot
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- // As this step is about cleaning up previously allocated resources, we'll allow the
- // app to do this cleanup even if the op is configured to be ignored.
- }
+ enforceChangePermission();
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
@@ -1373,10 +1345,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* @throws SecurityException if the caller does not have permission to write the sotap config
*/
@Override
- public void setWifiApConfiguration(WifiConfiguration wifiConfig, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return;
- }
+ public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
+ enforceChangePermission();
int uid = Binder.getCallingUid();
// only allow Settings UI to write the stored SoftApConfig
if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)) {
@@ -1408,10 +1378,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* see {@link android.net.wifi.WifiManager#disconnect()}
*/
@Override
- public void disconnect(String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return;
- }
+ public void disconnect() {
+ enforceChangePermission();
mLog.info("disconnect uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.disconnectCommand();
}
@@ -1420,10 +1388,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* see {@link android.net.wifi.WifiManager#reconnect()}
*/
@Override
- public void reconnect(String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return;
- }
+ public void reconnect() {
+ enforceChangePermission();
mLog.info("reconnect uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.reconnectCommand(new WorkSource(Binder.getCallingUid()));
}
@@ -1432,10 +1398,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* see {@link android.net.wifi.WifiManager#reassociate()}
*/
@Override
- public void reassociate(String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return;
- }
+ public void reassociate() {
+ enforceChangePermission();
mLog.info("reassociate uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.reassociateCommand();
}
@@ -1636,10 +1600,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* network if the operation succeeds, or {@code -1} if it fails
*/
@Override
- public int addOrUpdateNetwork(WifiConfiguration config, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return -1;
- }
+ public int addOrUpdateNetwork(WifiConfiguration config) {
+ enforceChangePermission();
mLog.info("addOrUpdateNetwork uid=%").c(Binder.getCallingUid()).flush();
// Previously, this API is overloaded for installing Passpoint profiles. Now
@@ -1658,7 +1620,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
config.enterpriseConfig.getClientCertificateChain());
passpointConfig.getCredential().setClientPrivateKey(
config.enterpriseConfig.getClientPrivateKey());
- if (!addOrUpdatePasspointConfiguration(passpointConfig, packageName)) {
+ if (!addOrUpdatePasspointConfiguration(passpointConfig)) {
Slog.e(TAG, "Failed to add Passpoint profile");
return -1;
}
@@ -1709,10 +1671,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* @return {@code true} if the operation succeeded
*/
@Override
- public boolean removeNetwork(int netId, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean removeNetwork(int netId) {
+ enforceChangePermission();
mLog.info("removeNetwork uid=%").c(Binder.getCallingUid()).flush();
// TODO Add private logging for netId b/33807876
if (mWifiStateMachineChannel != null) {
@@ -1731,10 +1691,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* @return {@code true} if the operation succeeded
*/
@Override
- public boolean enableNetwork(int netId, boolean disableOthers, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean enableNetwork(int netId, boolean disableOthers) {
+ enforceChangePermission();
// TODO b/33807876 Log netId
mLog.info("enableNetwork uid=% disableOthers=%")
.c(Binder.getCallingUid())
@@ -1756,10 +1714,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* @return {@code true} if the operation succeeded
*/
@Override
- public boolean disableNetwork(int netId, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean disableNetwork(int netId) {
+ enforceChangePermission();
// TODO b/33807876 Log netId
mLog.info("disableNetwork uid=%").c(Binder.getCallingUid()).flush();
@@ -1817,11 +1773,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* @return true on success or false on failure
*/
@Override
- public boolean addOrUpdatePasspointConfiguration(
- PasspointConfiguration config, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
+ enforceChangePermission();
mLog.info("addorUpdatePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
if (!mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_PASSPOINT)) {
@@ -1838,10 +1791,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* @return true on success or false on failure
*/
@Override
- public boolean removePasspointConfiguration(String fqdn, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean removePasspointConfiguration(String fqdn) {
+ enforceChangePermission();
mLog.info("removePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
if (!mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_PASSPOINT)) {
@@ -1913,10 +1864,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* TODO: deprecate this
*/
@Override
- public boolean saveConfiguration(String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean saveConfiguration() {
+ enforceChangePermission();
mLog.info("saveConfiguration uid=%").c(Binder.getCallingUid()).flush();
if (mWifiStateMachineChannel != null) {
return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
@@ -2112,13 +2061,9 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* an AsyncChannel communication with WifiService
*/
@Override
- public Messenger getWifiServiceMessenger(String packageName) throws RemoteException {
+ public Messenger getWifiServiceMessenger() {
enforceAccessPermission();
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- // We don't have a good way of creating a fake Messenger, and returning null would
- // immediately break callers.
- throw new RemoteException("Could not create wifi service messenger");
- }
+ enforceChangePermission();
mLog.info("getWifiServiceMessenger uid=%").c(Binder.getCallingUid()).flush();
return new Messenger(mClientHandler);
}
@@ -2127,11 +2072,9 @@ public class WifiServiceImpl extends IWifiManager.Stub {
* Disable an ephemeral network, i.e. network that is created thru a WiFi Scorer
*/
@Override
- public void disableEphemeralNetwork(String SSID, String packageName) {
+ public void disableEphemeralNetwork(String SSID) {
enforceAccessPermission();
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return;
- }
+ enforceChangePermission();
mLog.info("disableEphemeralNetwork uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.disableEphemeralNetwork(SSID);
}
@@ -2485,10 +2428,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
}
@Override
- public boolean setEnableAutoJoinWhenAssociated(boolean enabled, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
+ enforceChangePermission();
mLog.info("setEnableAutoJoinWhenAssociated uid=% enabled=%")
.c(Binder.getCallingUid())
.c(enabled).flush();
@@ -2517,7 +2458,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
}
@Override
- public void factoryReset(String packageName) {
+ public void factoryReset() {
enforceConnectivityInternalPermission();
mLog.info("factoryReset uid=%").c(Binder.getCallingUid()).flush();
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
@@ -2543,9 +2484,9 @@ public class WifiServiceImpl extends IWifiManager.Stub {
Binder.getCallingUid(), mWifiStateMachineChannel);
if (networks != null) {
for (WifiConfiguration config : networks) {
- removeNetwork(config.networkId, packageName);
+ removeNetwork(config.networkId);
}
- saveConfiguration(packageName);
+ saveConfiguration();
}
}
}
@@ -2719,4 +2660,33 @@ public class WifiServiceImpl extends IWifiManager.Stub {
restoreNetworks(wifiConfigurations);
Slog.d(TAG, "Restored supplicant backup data");
}
+
+ /**
+ * Starts subscription provisioning with a provider
+ *
+ * @param provider {@link OsuProvider} the provider to provision with
+ * @param callback {@link IProvisoningCallback} the callback object to inform status
+ */
+ @Override
+ public void startSubscriptionProvisioning(OsuProvider provider,
+ IProvisioningCallback callback) {
+ if (provider == null) {
+ throw new IllegalArgumentException("Provider must not be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("Callback must not be null");
+ }
+ enforceNetworkSettingsPermission();
+ if (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_PASSPOINT)) {
+ throw new UnsupportedOperationException("Passpoint not enabled");
+ }
+ final int uid = Binder.getCallingUid();
+ mLog.trace("startSubscriptionProvisioning uid=%").c(uid).flush();
+ if (mWifiStateMachine.syncStartSubscriptionProvisioning(uid, provider,
+ callback, mWifiStateMachineChannel)) {
+ mLog.trace("Subscription provisioning started with %")
+ .c(provider.toString()).flush();
+ }
+ }
}
diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java
index 2c8c0b76..b005923d 100644
--- a/com/android/server/wifi/WifiStateMachine.java
+++ b/com/android/server/wifi/WifiStateMachine.java
@@ -78,6 +78,7 @@ import android.net.wifi.WifiSsid;
import android.net.wifi.WpsInfo;
import android.net.wifi.WpsResult;
import android.net.wifi.WpsResult.Status;
+import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.p2p.IWifiP2pManager;
@@ -181,6 +182,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
private static final String EXTRA_OSU_ICON_QUERY_BSSID = "BSSID";
private static final String EXTRA_OSU_ICON_QUERY_FILENAME = "FILENAME";
+ private static final String EXTRA_OSU_PROVIDER = "OsuProvider";
private boolean mVerboseLoggingEnabled = false;
@@ -740,6 +742,9 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
/* Used to set the tx power limit for SAR during the start of a phone call. */
private static final int CMD_SELECT_TX_POWER_SCENARIO = BASE + 253;
+ // Start subscription provisioning with a given provider
+ private static final int CMD_START_SUBSCRIPTION_PROVISIONING = BASE + 254;
+
// For message logging.
private static final Class[] sMessageClasses = {
AsyncChannel.class, WifiStateMachine.class, DhcpClient.class };
@@ -1246,6 +1251,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
mWifiNative.enableVerboseLogging(verbose);
mWifiConfigManager.enableVerboseLogging(verbose);
mSupplicantStateTracker.enableVerboseLogging(verbose);
+ mPasspointManager.enableVerboseLogging(verbose);
}
private static final String SYSTEM_PROPERTY_LOG_CONTROL_WIFIHAL = "log.tag.WifiHAL";
@@ -2011,6 +2017,25 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
}
/**
+ * Start subscription provisioning synchronously
+ *
+ * @param provider {@link OsuProvider} the provider to provision with
+ * @param callback {@link IProvisioningCallback} callback for provisioning status
+ * @return boolean true indicates provisioning was started, false otherwise
+ */
+ public boolean syncStartSubscriptionProvisioning(int callingUid, OsuProvider provider,
+ IProvisioningCallback callback, AsyncChannel channel) {
+ Message msg = Message.obtain();
+ msg.what = CMD_START_SUBSCRIPTION_PROVISIONING;
+ msg.arg1 = callingUid;
+ msg.obj = callback;
+ msg.getData().putParcelable(EXTRA_OSU_PROVIDER, provider);
+ Message resultMsg = channel.sendMessageSynchronously(msg);
+ boolean result = resultMsg.arg1 != 0;
+ resultMsg.recycle();
+ return result;
+ }
+ /**
* Get connection statistics synchronously
*
* @param channel
@@ -3896,6 +3921,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
break;
case CMD_INITIALIZE:
ok = mWifiNative.initializeVendorHal(mVendorHalDeathRecipient);
+ mPasspointManager.initializeProvisioner(
+ mWifiInjector.getWifiServiceHandlerThread().getLooper());
replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
break;
case CMD_BOOT_COMPLETED:
@@ -4028,6 +4055,9 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
case CMD_GET_MATCHING_OSU_PROVIDERS:
replyToMessage(message, message.what, new ArrayList<OsuProvider>());
break;
+ case CMD_START_SUBSCRIPTION_PROVISIONING:
+ replyToMessage(message, message.what, 0);
+ break;
case CMD_IP_CONFIGURATION_SUCCESSFUL:
case CMD_IP_CONFIGURATION_LOST:
case CMD_IP_REACHABILITY_LOST:
@@ -5156,6 +5186,14 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
replyToMessage(message, message.what,
mPasspointManager.getMatchingOsuProviders((ScanResult) message.obj));
break;
+ case CMD_START_SUBSCRIPTION_PROVISIONING:
+ IProvisioningCallback callback = (IProvisioningCallback) message.obj;
+ OsuProvider provider =
+ (OsuProvider) message.getData().getParcelable(EXTRA_OSU_PROVIDER);
+ int res = mPasspointManager.startSubscriptionProvisioning(
+ message.arg1, provider, callback) ? 1 : 0;
+ replyToMessage(message, message.what, res);
+ break;
case CMD_RECONNECT:
WorkSource workSource = (WorkSource) message.obj;
mWifiConnectivityManager.forceConnectivityScan(workSource);
diff --git a/com/android/server/wifi/WificondControl.java b/com/android/server/wifi/WificondControl.java
index aa723d65..3dd63114 100644
--- a/com/android/server/wifi/WificondControl.java
+++ b/com/android/server/wifi/WificondControl.java
@@ -18,18 +18,21 @@ package com.android.server.wifi;
import android.annotation.NonNull;
import android.net.wifi.IApInterface;
+import android.net.wifi.IApInterfaceEventCallback;
import android.net.wifi.IClientInterface;
import android.net.wifi.IPnoScanEvent;
import android.net.wifi.IScanEvent;
import android.net.wifi.IWifiScannerImpl;
import android.net.wifi.IWificond;
import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiScanner;
import android.net.wifi.WifiSsid;
import android.os.Binder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.server.wifi.WifiNative.SoftApListener;
import com.android.server.wifi.hotspot2.NetworkDetail;
import com.android.server.wifi.util.InformationElementUtil;
import com.android.server.wifi.util.NativeUtil;
@@ -41,6 +44,7 @@ import com.android.server.wifi.wificond.PnoNetwork;
import com.android.server.wifi.wificond.PnoSettings;
import com.android.server.wifi.wificond.SingleScanSettings;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Set;
@@ -70,6 +74,7 @@ public class WificondControl {
private IWifiScannerImpl mWificondScanner;
private IScanEvent mScanEventHandler;
private IPnoScanEvent mPnoScanEventHandler;
+ private IApInterfaceEventCallback mApInterfaceListener;
private String mClientInterfaceName;
@@ -122,6 +127,22 @@ public class WificondControl {
}
}
+ /**
+ * Listener for AP Interface events.
+ */
+ private class ApInterfaceEventCallback extends IApInterfaceEventCallback.Stub {
+ private SoftApListener mSoftApListener;
+
+ ApInterfaceEventCallback(SoftApListener listener) {
+ mSoftApListener = listener;
+ }
+
+ @Override
+ public void onNumAssociatedStationsChanged(int numStations) {
+ mSoftApListener.onNumAssociatedStationsChanged(numStations);
+ }
+ }
+
/** Enable or disable verbose logging of WificondControl.
* @param enable True to enable verbose logging. False to disable verbose logging.
*/
@@ -251,12 +272,12 @@ public class WificondControl {
* @return Returns true on success.
*/
public boolean disableSupplicant() {
- if (mClientInterface == null) {
- Log.e(TAG, "No valid wificond client interface handler");
+ if (mWificond == null) {
+ Log.e(TAG, "No valid handler");
return false;
}
try {
- return mClientInterface.disableSupplicant();
+ return mWificond.disableSupplicant();
} catch (RemoteException e) {
Log.e(TAG, "Failed to disable supplicant due to remote exception");
}
@@ -268,13 +289,13 @@ public class WificondControl {
* @return Returns true on success.
*/
public boolean enableSupplicant() {
- if (mClientInterface == null) {
- Log.e(TAG, "No valid wificond client interface handler");
+ if (mWificond == null) {
+ Log.e(TAG, "No valid wificond handler");
return false;
}
try {
- return mClientInterface.enableSupplicant();
+ return mWificond.enableSupplicant();
} catch (RemoteException e) {
Log.e(TAG, "Failed to enable supplicant due to remote exception");
}
@@ -526,7 +547,6 @@ public class WificondControl {
}
}
-
/**
* Query the list of valid frequencies for the provided band.
* The result depends on the on the country code that has been set.
@@ -560,4 +580,90 @@ public class WificondControl {
}
return null;
}
+
+ /**
+ * Start Soft AP operation using the provided configuration.
+ *
+ * @param config Configuration to use for the soft ap created.
+ * @param listener Callback for AP events.
+ * @return true on success, false otherwise.
+ */
+ public boolean startSoftAp(WifiConfiguration config, SoftApListener listener) {
+ if (mApInterface == null) {
+ Log.e(TAG, "No valid ap interface handler");
+ return false;
+ }
+ int encryptionType = getIApInterfaceEncryptionType(config);
+ try {
+ // TODO(b/67745880) Note that config.SSID is intended to be either a
+ // hex string or "double quoted".
+ // However, it seems that whatever is handing us these configurations does not obey
+ // this convention.
+ boolean success = mApInterface.writeHostapdConfig(
+ config.SSID.getBytes(StandardCharsets.UTF_8), config.hiddenSSID,
+ config.apChannel, encryptionType,
+ (config.preSharedKey != null)
+ ? config.preSharedKey.getBytes(StandardCharsets.UTF_8)
+ : new byte[0]);
+ if (!success) {
+ Log.e(TAG, "Failed to write hostapd configuration");
+ return false;
+ }
+ mApInterfaceListener = new ApInterfaceEventCallback(listener);
+ success = mApInterface.startHostapd(mApInterfaceListener);
+ if (!success) {
+ Log.e(TAG, "Failed to start hostapd.");
+ return false;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in starting soft AP: " + e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Stop the ongoing Soft AP operation.
+ *
+ * @return true on success, false otherwise.
+ */
+ public boolean stopSoftAp() {
+ if (mApInterface == null) {
+ Log.e(TAG, "No valid ap interface handler");
+ return false;
+ }
+ try {
+ boolean success = mApInterface.stopHostapd();
+ if (!success) {
+ Log.e(TAG, "Failed to stop hostapd.");
+ return false;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in stopping soft AP: " + e);
+ return false;
+ }
+ mApInterfaceListener = null;
+ return true;
+ }
+
+ private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) {
+ int encryptionType;
+ switch (localConfig.getAuthType()) {
+ case WifiConfiguration.KeyMgmt.NONE:
+ encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
+ break;
+ case WifiConfiguration.KeyMgmt.WPA_PSK:
+ encryptionType = IApInterface.ENCRYPTION_TYPE_WPA;
+ break;
+ case WifiConfiguration.KeyMgmt.WPA2_PSK:
+ encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2;
+ break;
+ default:
+ // We really shouldn't default to None, but this was how NetworkManagementService
+ // used to do this.
+ encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
+ break;
+ }
+ return encryptionType;
+ }
}
diff --git a/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java b/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
index 86f4e37c..3358a4a2 100644
--- a/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
+++ b/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
@@ -244,13 +244,20 @@ public class WifiAwareDiscoverySessionState {
* (usually not used in the match decisions).
* @param matchFilter The filter from the discovery advertisement (which was
* used in the match decision).
+ * @param rangingIndication Bit mask indicating the type of ranging event triggered.
+ * @param rangeMm The range to the peer in mm (valid if rangingIndication specifies ingress
+ * or egress events - i.e. non-zero).
*/
public void onMatch(int requestorInstanceId, byte[] peerMac, byte[] serviceSpecificInfo,
- byte[] matchFilter) {
+ byte[] matchFilter, int rangingIndication, int rangeMm) {
int peerId = getPeerIdOrAddIfNew(requestorInstanceId, peerMac);
try {
- mCallback.onMatch(peerId, serviceSpecificInfo, matchFilter);
+ if (rangingIndication == 0) {
+ mCallback.onMatch(peerId, serviceSpecificInfo, matchFilter);
+ } else {
+ mCallback.onMatchWithDistance(peerId, serviceSpecificInfo, matchFilter, rangeMm);
+ }
} catch (RemoteException e) {
Log.w(TAG, "onMatch: RemoteException (FYI): " + e);
}
diff --git a/com/android/server/wifi/aware/WifiAwareNativeApi.java b/com/android/server/wifi/aware/WifiAwareNativeApi.java
index a6e724f7..c8b5fced 100644
--- a/com/android/server/wifi/aware/WifiAwareNativeApi.java
+++ b/com/android/server/wifi/aware/WifiAwareNativeApi.java
@@ -26,6 +26,7 @@ import android.hardware.wifi.V1_0.NanEnableRequest;
import android.hardware.wifi.V1_0.NanInitiateDataPathRequest;
import android.hardware.wifi.V1_0.NanMatchAlg;
import android.hardware.wifi.V1_0.NanPublishRequest;
+import android.hardware.wifi.V1_0.NanRangingIndication;
import android.hardware.wifi.V1_0.NanRespondToDataPathIndicationRequest;
import android.hardware.wifi.V1_0.NanSubscribeRequest;
import android.hardware.wifi.V1_0.NanTransmitFollowupRequest;
@@ -430,11 +431,13 @@ public class WifiAwareNativeApi implements WifiAwareShellCommand.DelegatedShellC
req.baseConfigs.disableMatchExpirationIndication = true;
req.baseConfigs.disableFollowupReceivedIndication = false;
- // TODO: configure ranging and security
- req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
- req.baseConfigs.rangingRequired = false;
req.autoAcceptDataPathRequests = false;
+ req.baseConfigs.rangingRequired = publishConfig.mEnableRanging;
+
+ // TODO: configure security
+ req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
+
req.publishType = publishConfig.mPublishType;
req.txType = NanTxType.BROADCAST;
@@ -493,9 +496,23 @@ public class WifiAwareNativeApi implements WifiAwareShellCommand.DelegatedShellC
req.baseConfigs.disableMatchExpirationIndication = true;
req.baseConfigs.disableFollowupReceivedIndication = false;
- // TODO: configure ranging and security
+ req.baseConfigs.rangingRequired =
+ subscribeConfig.mMinDistanceMmSet || subscribeConfig.mMaxDistanceMmSet;
+ req.baseConfigs.configRangingIndications = 0;
+ // TODO: b/69428593 remove correction factors once HAL converted from CM to MM
+ if (subscribeConfig.mMinDistanceMmSet) {
+ req.baseConfigs.distanceIngressCm = (short) Math.min(
+ subscribeConfig.mMinDistanceMm / 10, Short.MAX_VALUE);
+ req.baseConfigs.configRangingIndications |= NanRangingIndication.INGRESS_MET_MASK;
+ }
+ if (subscribeConfig.mMaxDistanceMmSet) {
+ req.baseConfigs.distanceEgressCm = (short) Math.min(subscribeConfig.mMaxDistanceMm / 10,
+ Short.MAX_VALUE);
+ req.baseConfigs.configRangingIndications |= NanRangingIndication.EGRESS_MET_MASK;
+ }
+
+ // TODO: configure security
req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
- req.baseConfigs.rangingRequired = false;
req.subscribeType = subscribeConfig.mSubscribeType;
diff --git a/com/android/server/wifi/aware/WifiAwareNativeCallback.java b/com/android/server/wifi/aware/WifiAwareNativeCallback.java
index 2121160b..b45978b2 100644
--- a/com/android/server/wifi/aware/WifiAwareNativeCallback.java
+++ b/com/android/server/wifi/aware/WifiAwareNativeCallback.java
@@ -405,13 +405,17 @@ public class WifiAwareNativeCallback extends IWifiNanIfaceEventCallback.Stub imp
+ (event.serviceSpecificInfo == null ? 0 : event.serviceSpecificInfo.size())
+ ", matchFilter=" + Arrays.toString(
convertArrayListToNativeByteArray(event.matchFilter)) + ", mf.size()=" + (
- event.matchFilter == null ? 0 : event.matchFilter.size()));
+ event.matchFilter == null ? 0 : event.matchFilter.size())
+ + ", rangingIndicationType=" + event.rangingIndicationType
+ + ", rangingMeasurementInCm=" + event.rangingMeasurementInCm);
}
incrementCbCount(CB_EV_MATCH);
+ // TODO: b/69428593 get rid of conversion once HAL moves from CM to MM
mWifiAwareStateManager.onMatchNotification(event.discoverySessionId, event.peerId,
event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo),
- convertArrayListToNativeByteArray(event.matchFilter));
+ convertArrayListToNativeByteArray(event.matchFilter), event.rangingIndicationType,
+ event.rangingMeasurementInCm * 10);
}
@Override
diff --git a/com/android/server/wifi/aware/WifiAwareNativeManager.java b/com/android/server/wifi/aware/WifiAwareNativeManager.java
index 8659a775..d6bec5f1 100644
--- a/com/android/server/wifi/aware/WifiAwareNativeManager.java
+++ b/com/android/server/wifi/aware/WifiAwareNativeManager.java
@@ -16,6 +16,7 @@
package com.android.server.wifi.aware;
+import android.annotation.NonNull;
import android.hardware.wifi.V1_0.IWifiNanIface;
import android.hardware.wifi.V1_0.IfaceType;
import android.hardware.wifi.V1_0.WifiStatus;
@@ -150,7 +151,7 @@ public class WifiAwareNativeManager {
private class InterfaceDestroyedListener implements
HalDeviceManager.InterfaceDestroyedListener {
@Override
- public void onDestroyed() {
+ public void onDestroyed(@NonNull String ifaceName) {
if (DBG) Log.d(TAG, "Interface was destroyed");
awareIsDown();
}
diff --git a/com/android/server/wifi/aware/WifiAwareStateManager.java b/com/android/server/wifi/aware/WifiAwareStateManager.java
index 0efe7361..89d9a904 100644
--- a/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -183,6 +183,8 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
private static final String MESSAGE_BUNDLE_KEY_PMK = "pmk";
private static final String MESSAGE_BUNDLE_KEY_PASSPHRASE = "passphrase";
private static final String MESSAGE_BUNDLE_KEY_OOB = "out_of_band";
+ private static final String MESSAGE_RANGING_INDICATION = "ranging_indication";
+ private static final String MESSAGE_RANGE_MM = "range_mm";
private WifiAwareNativeApi mWifiAwareNativeApi;
private WifiAwareNativeManager mWifiAwareNativeManager;
@@ -944,7 +946,7 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
* matching service (to the one we were looking for).
*/
public void onMatchNotification(int pubSubId, int requestorInstanceId, byte[] peerMac,
- byte[] serviceSpecificInfo, byte[] matchFilter) {
+ byte[] serviceSpecificInfo, byte[] matchFilter, int rangingIndication, int rangeMm) {
Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
msg.arg1 = NOTIFICATION_TYPE_MATCH;
msg.arg2 = pubSubId;
@@ -952,6 +954,8 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac);
msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA, serviceSpecificInfo);
msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA, matchFilter);
+ msg.getData().putInt(MESSAGE_RANGING_INDICATION, rangingIndication);
+ msg.getData().putInt(MESSAGE_RANGE_MM, rangeMm);
mSm.sendMessage(msg);
}
@@ -1255,9 +1259,11 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
byte[] serviceSpecificInfo = msg.getData()
.getByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA);
byte[] matchFilter = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA);
+ int rangingIndication = msg.getData().getInt(MESSAGE_RANGING_INDICATION);
+ int rangeMm = msg.getData().getInt(MESSAGE_RANGE_MM);
onMatchLocal(pubSubId, requestorInstanceId, peerMac, serviceSpecificInfo,
- matchFilter);
+ matchFilter, rangingIndication, rangeMm);
break;
}
case NOTIFICATION_TYPE_SESSION_TERMINATED: {
@@ -2788,13 +2794,14 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
}
private void onMatchLocal(int pubSubId, int requestorInstanceId, byte[] peerMac,
- byte[] serviceSpecificInfo, byte[] matchFilter) {
+ byte[] serviceSpecificInfo, byte[] matchFilter, int rangingIndication, int rangeMm) {
if (VDBG) {
Log.v(TAG,
"onMatch: pubSubId=" + pubSubId + ", requestorInstanceId=" + requestorInstanceId
+ ", peerDiscoveryMac=" + String.valueOf(HexEncoding.encode(peerMac))
+ ", serviceSpecificInfo=" + Arrays.toString(serviceSpecificInfo)
- + ", matchFilter=" + Arrays.toString(matchFilter));
+ + ", matchFilter=" + Arrays.toString(matchFilter)
+ + ", rangingIndication=" + rangingIndication + ", rangeMm=" + rangeMm);
}
Pair<WifiAwareClientState, WifiAwareDiscoverySessionState> data =
@@ -2804,7 +2811,8 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
return;
}
- data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo, matchFilter);
+ data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo, matchFilter,
+ rangingIndication, rangeMm);
}
private void onSessionTerminatedLocal(int pubSubId, boolean isPublish, int reason) {
diff --git a/com/android/server/wifi/hotspot2/OsuNetworkConnection.java b/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
new file mode 100644
index 00000000..f1bf1175
--- /dev/null
+++ b/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
@@ -0,0 +1,270 @@
+/*
+ * 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.wifi.hotspot2;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Network;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Responsible for setup/monitor on Wi-Fi state and connection to the OSU AP.
+ */
+public class OsuNetworkConnection {
+ private static final String TAG = "OsuNetworkConnection";
+
+ private final Context mContext;
+
+ private boolean mVerboseLoggingEnabled = false;
+ private WifiManager mWifiManager;
+ private Callbacks mCallbacks;
+ private boolean mConnected = false;
+ private int mNetworkId = -1;
+ private boolean mWifiEnabled = false;
+
+ /**
+ * Callbacks on Wi-Fi connection state changes.
+ */
+ public interface Callbacks {
+ /**
+ * Invoked when network connection is established with IP connectivity.
+ *
+ * @param network {@link Network} associated with the connected network.
+ */
+ void onConnected(Network network);
+
+ /**
+ * Invoked when the targeted network is disconnected.
+ */
+ void onDisconnected();
+
+ /**
+ * Invoked when a timer tracking connection request is not reset by successfull connection.
+ */
+ void onTimeOut();
+
+ /**
+ * Invoked when Wifi is enabled.
+ */
+ void onWifiEnabled();
+
+ /**
+ * Invoked when Wifi is disabled.
+ */
+ void onWifiDisabled();
+ }
+
+ /**
+ * Create an instance of {@link NetworkConnection} for the specified Wi-Fi network.
+ * @param context The application context
+ */
+ public OsuNetworkConnection(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Called to initialize tracking of wifi state and network events by registering for the
+ * corresponding intents.
+ */
+ public void init(Handler handler) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ handleNetworkStateChanged(
+ intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO),
+ intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO));
+ } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN);
+ handleWifiStateChanged(state);
+ }
+ }
+ };
+ mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ mContext.registerReceiver(receiver, filter, null, handler);
+ mWifiEnabled = mWifiManager.isWifiEnabled();
+ }
+
+ /**
+ * Disconnect, if required in the two cases
+ * - still connected to the OSU AP
+ * - connection to OSU AP was requested and in progress
+ */
+ public void disconnectIfNeeded() {
+ if (mNetworkId < 0) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "No connection to tear down");
+ }
+ return;
+ }
+ mWifiManager.removeNetwork(mNetworkId);
+ mNetworkId = -1;
+ mConnected = false;
+ if (mCallbacks != null) {
+ mCallbacks.onDisconnected();
+ }
+ }
+
+ /**
+ * Register for network and Wifi state events
+ * @param callbacks The callbacks to be invoked on network change events
+ */
+ public void setEventCallback(Callbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ /**
+ * Connect to a OSU Wi-Fi network specified by the given SSID. The security type of the Wi-Fi
+ * network is either open or OSEN (OSU Server-only authenticated layer 2 Encryption Network).
+ * When network access identifier is provided, OSEN is used.
+ *
+ * @param ssid The SSID to connect to
+ * @param nai Network access identifier of the network
+ *
+ * @return boolean true if connection was successfully initiated
+ */
+ public boolean connect(WifiSsid ssid, String nai) {
+ if (mConnected) {
+ if (mVerboseLoggingEnabled) {
+ // Already connected
+ Log.v(TAG, "Connect called twice");
+ }
+ return true;
+ }
+ if (!mWifiManager.isWifiEnabled()) {
+ Log.w(TAG, "Wifi is not enabled");
+ return false;
+ }
+ WifiConfiguration config = new WifiConfiguration();
+ config.SSID = "\"" + ssid.toString() + "\"";
+ if (TextUtils.isEmpty(nai)) {
+ config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ } else {
+ // TODO(sohanirao): Handle OSEN.
+ Log.w(TAG, "OSEN not supported");
+ return false;
+ }
+ mNetworkId = mWifiManager.addNetwork(config);
+ if (mNetworkId < 0) {
+ Log.e(TAG, "Unable to add network");
+ return false;
+ }
+ if (!mWifiManager.enableNetwork(mNetworkId, true)) {
+ Log.e(TAG, "Unable to enable network " + mNetworkId);
+ disconnectIfNeeded();
+ return false;
+ }
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Current network ID " + mNetworkId);
+ }
+ // TODO(sohanirao): setup alarm to time out the connection attempt.
+ return true;
+ }
+
+ /**
+ * Method to update logging level in this class
+ * @param verbose more than 0 enables verbose logging
+ */
+ public void enableVerboseLogging(int verbose) {
+ mVerboseLoggingEnabled = verbose > 0 ? true : false;
+ }
+
+ /**
+ * Handle network state changed events.
+ *
+ * @param networkInfo {@link NetworkInfo} indicating the current network state
+ * @param wifiInfo {@link WifiInfo} associated with the current network when connected
+ */
+ private void handleNetworkStateChanged(NetworkInfo networkInfo, WifiInfo wifiInfo) {
+ if (networkInfo == null) {
+ Log.w(TAG, "NetworkInfo not provided for network state changed event");
+ return;
+ }
+ switch (networkInfo.getDetailedState()) {
+ case CONNECTED:
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Connected event received");
+ }
+ if (wifiInfo == null) {
+ Log.w(TAG, "WifiInfo not provided for network state changed event");
+ return;
+ }
+ handleConnectedEvent(wifiInfo);
+ break;
+ case DISCONNECTED:
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Disconnected event received");
+ }
+ disconnectIfNeeded();
+ break;
+ default:
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Ignore uninterested state: " + networkInfo.getDetailedState());
+ }
+ break;
+ }
+ }
+
+ /**
+ * Handle network connected event.
+ *
+ * @param wifiInfo {@link WifiInfo} associated with the current connection
+ */
+ private void handleConnectedEvent(WifiInfo wifiInfo) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "handleConnectedEvent " + wifiInfo.getNetworkId());
+ }
+ if (wifiInfo.getNetworkId() != mNetworkId) {
+ disconnectIfNeeded();
+ return;
+ }
+ if (!mConnected) {
+ mConnected = true;
+ if (mCallbacks != null) {
+ mCallbacks.onConnected(mWifiManager.getCurrentNetwork());
+ }
+ }
+ }
+
+ /**
+ * Handle Wifi state change event
+ */
+ private void handleWifiStateChanged(int state) {
+ if (state == WifiManager.WIFI_STATE_DISABLED && mWifiEnabled) {
+ mWifiEnabled = false;
+ if (mCallbacks != null) mCallbacks.onWifiDisabled();
+ }
+ if (state == WifiManager.WIFI_STATE_ENABLED && !mWifiEnabled) {
+ mWifiEnabled = true;
+ if (mCallbacks != null) mCallbacks.onWifiEnabled();
+ }
+ }
+}
diff --git a/com/android/server/wifi/hotspot2/PasspointManager.java b/com/android/server/wifi/hotspot2/PasspointManager.java
index 3580c839..fec3dd81 100644
--- a/com/android/server/wifi/hotspot2/PasspointManager.java
+++ b/com/android/server/wifi/hotspot2/PasspointManager.java
@@ -33,8 +33,10 @@ import android.graphics.drawable.Icon;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.os.Looper;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -99,6 +101,7 @@ public class PasspointManager {
private final WifiConfigManager mWifiConfigManager;
private final CertificateVerifier mCertVerifier;
private final WifiMetrics mWifiMetrics;
+ private final PasspointProvisioner mPasspointProvisioner;
// Counter used for assigning unique identifier to each provider.
private long mProviderIndex;
@@ -212,10 +215,27 @@ public class PasspointManager {
mProviderIndex = 0;
wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigStoreData(
mKeyStore, mSimAccessor, new DataSourceHandler()));
+ mPasspointProvisioner = objectFactory.makePasspointProvisioner(context,
+ objectFactory.makeOsuNetworkConnection(context));
sPasspointManager = this;
}
/**
+ * Initializes the provisioning flow with a looper
+ */
+ public void initializeProvisioner(Looper looper) {
+ mPasspointProvisioner.init(looper);
+ }
+
+ /**
+ * Enable verbose logging
+ * @param verbose more than 0 enables verbose logging
+ */
+ public void enableVerboseLogging(int verbose) {
+ mPasspointProvisioner.enableVerboseLogging(verbose);
+ }
+
+ /**
* Add or update a Passpoint provider with the given configuration.
*
* Each provider is uniquely identified by its FQDN (Fully Qualified Domain Name).
@@ -704,4 +724,16 @@ public class PasspointManager {
mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider);
return true;
}
+
+ /**
+ * Start the subscription provisioning flow with a provider.
+ * @param callingUid integer indicating the uid of the caller
+ * @param provider {@link OsuProvider} the provider to subscribe to
+ * @param callback {@link IProvisioningCallback} callback to update status to the caller
+ * @return boolean return value from the provisioning method
+ */
+ public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
+ IProvisioningCallback callback) {
+ return mPasspointProvisioner.startSubscriptionProvisioning(callingUid, provider, callback);
+ }
}
diff --git a/com/android/server/wifi/hotspot2/PasspointObjectFactory.java b/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
index c41c49ac..71fc47a2 100644
--- a/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
+++ b/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
@@ -16,6 +16,7 @@
package com.android.server.wifi.hotspot2;
+import android.content.Context;
import android.net.wifi.hotspot2.PasspointConfiguration;
import com.android.server.wifi.Clock;
@@ -95,4 +96,25 @@ public class PasspointObjectFactory{
public CertificateVerifier makeCertificateVerifier() {
return new CertificateVerifier();
}
+
+ /**
+ * Create an instance of {@link PasspointProvisioner}.
+ *
+ * @param context
+ * @return {@link PasspointProvisioner}
+ */
+ public PasspointProvisioner makePasspointProvisioner(Context context,
+ OsuNetworkConnection osuNetworkConnection) {
+ return new PasspointProvisioner(context, osuNetworkConnection);
+ }
+
+ /**
+ * Create an instance of {@link OsuNetworkConnection}.
+ *
+ * @param context
+ * @return {@link OsuNetworkConnection}
+ */
+ public OsuNetworkConnection makeOsuNetworkConnection(Context context) {
+ return new OsuNetworkConnection(context);
+ }
}
diff --git a/com/android/server/wifi/hotspot2/PasspointProvisioner.java b/com/android/server/wifi/hotspot2/PasspointProvisioner.java
new file mode 100644
index 00000000..d3bb7731
--- /dev/null
+++ b/com/android/server/wifi/hotspot2/PasspointProvisioner.java
@@ -0,0 +1,309 @@
+/*
+ * 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.wifi.hotspot2;
+
+import android.content.Context;
+import android.net.Network;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.OsuProvider;
+import android.net.wifi.hotspot2.ProvisioningCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Provides methods to carry out provisioning flow
+ */
+public class PasspointProvisioner {
+ private static final String TAG = "PasspointProvisioner";
+
+ private static final int PROVISIONING_STATUS = 0;
+ private static final int PROVISIONING_FAILURE = 1;
+
+ private final Context mContext;
+ private final ProvisioningStateMachine mProvisioningStateMachine;
+ private final OsuNetworkCallbacks mOsuNetworkCallbacks;
+ private final OsuNetworkConnection mOsuNetworkConnection;
+
+ private int mCallingUid;
+ private boolean mVerboseLoggingEnabled = false;
+
+ PasspointProvisioner(Context context, OsuNetworkConnection osuNetworkConnection) {
+ mContext = context;
+ mOsuNetworkConnection = osuNetworkConnection;
+ mProvisioningStateMachine = new ProvisioningStateMachine();
+ mOsuNetworkCallbacks = new OsuNetworkCallbacks();
+ }
+
+ /**
+ * Sets up for provisioning
+ * @param looper Looper on which the Provisioning state machine will run
+ */
+ public void init(Looper looper) {
+ mProvisioningStateMachine.start(new Handler(looper));
+ mOsuNetworkConnection.init(mProvisioningStateMachine.getHandler());
+ }
+
+ /**
+ * Enable verbose logging to help debug failures
+ * @param level integer indicating verbose logging enabled if > 0
+ */
+ public void enableVerboseLogging(int level) {
+ mVerboseLoggingEnabled = (level > 0) ? true : false;
+ mOsuNetworkConnection.enableVerboseLogging(level);
+ }
+
+ /**
+ * Start provisioning flow with a given provider.
+ * @param callingUid calling uid.
+ * @param provider {@link OsuProvider} to provision with.
+ * @param callback {@link IProvisioningCallback} to provide provisioning status.
+ * @return boolean value, true if provisioning was started, false otherwise.
+ *
+ * Implements HS2.0 provisioning flow with a given HS2.0 provider.
+ */
+ public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
+ IProvisioningCallback callback) {
+ mCallingUid = callingUid;
+
+ Log.v(TAG, "Provisioning started with " + provider.toString());
+
+ mProvisioningStateMachine.getHandler().post(() -> {
+ mProvisioningStateMachine.startProvisioning(provider, callback);
+ });
+
+ return true;
+ }
+
+ /**
+ * Handles the provisioning flow state transitions
+ */
+ class ProvisioningStateMachine {
+ private static final String TAG = "ProvisioningStateMachine";
+
+ private static final int DEFAULT_STATE = 0;
+ private static final int INITIAL_STATE = 1;
+ private static final int WAITING_TO_CONNECT = 2;
+ private static final int OSU_AP_CONNECTED = 3;
+ private static final int FAILED_STATE = 4;
+
+ private OsuProvider mOsuProvider;
+ private IProvisioningCallback mProvisioningCallback;
+ private Handler mHandler;
+ private int mState;
+
+ ProvisioningStateMachine() {
+ mState = DEFAULT_STATE;
+ }
+
+ /**
+ * Initializes and starts the state machine with a handler to handle incoming events
+ */
+ public void start(Handler handler) {
+ mHandler = handler;
+ changeState(INITIAL_STATE);
+ }
+
+ /**
+ * Returns the handler on which a runnable can be posted
+ * @return Handler State Machine's handler
+ */
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Start Provisioning with the Osuprovider and invoke callbacks
+ * @param provider OsuProvider to provision with
+ * @param callback IProvisioningCallback to invoke callbacks on
+ */
+ public void startProvisioning(OsuProvider provider, IProvisioningCallback callback) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "startProvisioning received in state=" + mState);
+ }
+ if (mState != INITIAL_STATE) {
+ Log.v(TAG, "State Machine needs to be reset before starting provisioning");
+ resetStateMachine();
+ }
+ mProvisioningCallback = callback;
+ mOsuProvider = provider;
+
+ // Register for network and wifi state events during provisioning flow
+ mOsuNetworkConnection.setEventCallback(mOsuNetworkCallbacks);
+
+ if (mOsuNetworkConnection.connect(mOsuProvider.getOsuSsid(),
+ mOsuProvider.getNetworkAccessIdentifier())) {
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_AP_CONNECTING);
+ changeState(WAITING_TO_CONNECT);
+ } else {
+ invokeProvisioningCallback(PROVISIONING_FAILURE,
+ ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+ enterFailedState();
+ }
+ }
+
+ /**
+ * Handle Wifi Disable event
+ */
+ public void handleWifiDisabled() {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Wifi Disabled in state=" + mState);
+ }
+ if (mState == INITIAL_STATE || mState == FAILED_STATE) {
+ Log.w(TAG, "Wifi Disable unhandled in state=" + mState);
+ return;
+ }
+ invokeProvisioningCallback(PROVISIONING_FAILURE,
+ ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+ enterFailedState();
+ }
+
+ private void resetStateMachine() {
+ // Set to null so that no callbacks are invoked during reset
+ mProvisioningCallback = null;
+ if (mState != INITIAL_STATE || mState != FAILED_STATE) {
+ // Transition through Failed state to clean up
+ enterFailedState();
+ }
+ changeState(INITIAL_STATE);
+ }
+
+ /**
+ * Connected event received
+ * @param network Network object for this connection
+ */
+ public void handleConnectedEvent(Network network) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Connected event received in state=" + mState);
+ }
+ if (mState != WAITING_TO_CONNECT) {
+ // Not waiting for a connection
+ Log.w(TAG, "Connection event unhandled in state=" + mState);
+ return;
+ }
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_AP_CONNECTED);
+ changeState(OSU_AP_CONNECTED);
+ }
+
+ /**
+ * Disconnect event received
+ */
+ public void handleDisconnect() {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Connection failed in state=" + mState);
+ }
+ if (mState == INITIAL_STATE || mState == FAILED_STATE) {
+ Log.w(TAG, "Disconnect event unhandled in state=" + mState);
+ return;
+ }
+ invokeProvisioningCallback(PROVISIONING_FAILURE,
+ ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+ enterFailedState();
+ }
+
+ private void changeState(int nextState) {
+ if (nextState != mState) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Changing state from " + mState + " -> " + nextState);
+ }
+ mState = nextState;
+ }
+ }
+
+ private void invokeProvisioningCallback(int callbackType, int status) {
+ if (mProvisioningCallback == null) {
+ Log.e(TAG, "Provisioning callback " + callbackType + " with status " + status
+ + " not invoked, callback is null");
+ return;
+ }
+ try {
+ if (callbackType == PROVISIONING_STATUS) {
+ mProvisioningCallback.onProvisioningStatus(status);
+ } else {
+ mProvisioningCallback.onProvisioningFailure(status);
+ }
+ } catch (RemoteException e) {
+ if (callbackType == PROVISIONING_STATUS) {
+ Log.e(TAG, "Remote Exception while posting Provisioning status " + status);
+ } else {
+ Log.e(TAG, "Remote Exception while posting Provisioning failure " + status);
+ }
+ }
+ }
+
+ private void enterFailedState() {
+ changeState(FAILED_STATE);
+ mOsuNetworkConnection.setEventCallback(null);
+ mOsuNetworkConnection.disconnectIfNeeded();
+ }
+ }
+
+ /**
+ * Callbacks for network and wifi events
+ */
+ class OsuNetworkCallbacks implements OsuNetworkConnection.Callbacks {
+
+ OsuNetworkCallbacks() {
+ }
+
+ @Override
+ public void onConnected(Network network) {
+ Log.v(TAG, "onConnected to " + network);
+ if (network == null) {
+ mProvisioningStateMachine.getHandler().post(() -> {
+ mProvisioningStateMachine.handleDisconnect();
+ });
+ } else {
+ mProvisioningStateMachine.getHandler().post(() -> {
+ mProvisioningStateMachine.handleConnectedEvent(network);
+ });
+ }
+ }
+
+ @Override
+ public void onDisconnected() {
+ Log.v(TAG, "onDisconnected");
+ mProvisioningStateMachine.getHandler().post(() -> {
+ mProvisioningStateMachine.handleDisconnect();
+ });
+ }
+
+ @Override
+ public void onTimeOut() {
+ Log.v(TAG, "Timed out waiting for connection to OSU AP");
+ mProvisioningStateMachine.getHandler().post(() -> {
+ mProvisioningStateMachine.handleDisconnect();
+ });
+ }
+
+ @Override
+ public void onWifiEnabled() {
+ Log.v(TAG, "onWifiEnabled");
+ }
+
+ @Override
+ public void onWifiDisabled() {
+ Log.v(TAG, "onWifiDisabled");
+ mProvisioningStateMachine.getHandler().post(() -> {
+ mProvisioningStateMachine.handleWifiDisabled();
+ });
+ }
+ }
+}
diff --git a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index da3da7c2..751737f4 100644
--- a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.wifi.p2p;
+import android.annotation.NonNull;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -524,7 +525,7 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub {
}
mHalDeviceManager = mWifiInjector.getHalDeviceManager();
}
- mIWifiP2pIface = mHalDeviceManager.createP2pIface(() -> {
+ mIWifiP2pIface = mHalDeviceManager.createP2pIface((@NonNull String ifaceName) -> {
if (DBG) Log.d(TAG, "IWifiP2pIface destroyedListener");
synchronized (mLock) {
mIWifiP2pIface = null;
diff --git a/com/android/server/wifi/rtt/RttNative.java b/com/android/server/wifi/rtt/RttNative.java
index fe1829f8..dd54c248 100644
--- a/com/android/server/wifi/rtt/RttNative.java
+++ b/com/android/server/wifi/rtt/RttNative.java
@@ -46,7 +46,7 @@ import java.util.List;
*/
public class RttNative extends IWifiRttControllerEventCallback.Stub {
private static final String TAG = "RttNative";
- private static final boolean VDBG = true; // STOPSHIP if true
+ private static final boolean VDBG = false; // STOPSHIP if true
private final RttServiceImpl mRttService;
private final HalDeviceManager mHalDeviceManager;
diff --git a/com/android/server/wifi/rtt/RttService.java b/com/android/server/wifi/rtt/RttService.java
index 0790deed..5c2cec15 100644
--- a/com/android/server/wifi/rtt/RttService.java
+++ b/com/android/server/wifi/rtt/RttService.java
@@ -66,7 +66,8 @@ public class RttService extends SystemService {
Context.WIFI_AWARE_SERVICE);
RttNative rttNative = new RttNative(mImpl, halDeviceManager);
- mImpl.start(handlerThread.getLooper(), awareBinder, rttNative, wifiPermissionsUtil);
+ mImpl.start(handlerThread.getLooper(), wifiInjector.getClock(), awareBinder, rttNative,
+ wifiPermissionsUtil);
}
}
}
diff --git a/com/android/server/wifi/rtt/RttServiceImpl.java b/com/android/server/wifi/rtt/RttServiceImpl.java
index 36caab74..401daabb 100644
--- a/com/android/server/wifi/rtt/RttServiceImpl.java
+++ b/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.wifi.rtt;
+import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -39,13 +40,13 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.WakeupMessage;
+import com.android.server.wifi.Clock;
import com.android.server.wifi.util.NativeUtil;
import com.android.server.wifi.util.WifiPermissionsUtil;
@@ -66,12 +67,14 @@ import java.util.Map;
*/
public class RttServiceImpl extends IWifiRttManager.Stub {
private static final String TAG = "RttServiceImpl";
- private static final boolean VDBG = true; // STOPSHIP if true
+ private static final boolean VDBG = false; // STOPSHIP if true
private final Context mContext;
+ private Clock mClock;
private IWifiAwareManager mAwareBinder;
private RttNative mRttNative;
private WifiPermissionsUtil mWifiPermissionsUtil;
+ private ActivityManager mActivityManager;
private PowerManager mPowerManager;
private RttServiceSynchronized mRttServiceSynchronized;
@@ -79,7 +82,10 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
@VisibleForTesting
public static final String HAL_RANGING_TIMEOUT_TAG = TAG + " HAL Ranging Timeout";
- private static final long HAL_RANGING_TIMEOUT_MS = 5_000;
+ private static final long HAL_RANGING_TIMEOUT_MS = 5_000; // 5 sec
+
+ // TODO: b/69323456 convert to a settable value
+ /* package */ static final long BACKGROUND_PROCESS_EXEC_GAP_MS = 1_800_000; // 30 min
public RttServiceImpl(Context context) {
mContext = context;
@@ -93,17 +99,21 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
* Initializes the RTT service (usually with objects from an injector).
*
* @param looper The looper on which to synchronize operations.
+ * @param clock A mockable clock.
* @param awareBinder The Wi-Fi Aware service (binder) if supported on the system.
* @param rttNative The Native interface to the HAL.
* @param wifiPermissionsUtil Utility for permission checks.
*/
- public void start(Looper looper, IWifiAwareManager awareBinder, RttNative rttNative,
+ public void start(Looper looper, Clock clock, IWifiAwareManager awareBinder,
+ RttNative rttNative,
WifiPermissionsUtil wifiPermissionsUtil) {
+ mClock = clock;
mAwareBinder = awareBinder;
mRttNative = rttNative;
mWifiPermissionsUtil = wifiPermissionsUtil;
mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative);
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
mPowerManager = mContext.getSystemService(PowerManager.class);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
@@ -323,6 +333,7 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
private RttNative mRttNative;
private int mNextCommandId = 1000;
+ private Map<Integer, RttRequesterInfo> mRttRequesterInfo = new HashMap<>();
private List<RttRequestInfo> mRttRequestQueue = new LinkedList<>();
private WakeupMessage mRangingTimeoutMessage = null;
@@ -540,10 +551,23 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
return;
}
+ if (!preExecThrottleCheck(nextRequest.workSource)) {
+ Log.w(TAG, "RttServiceSynchronized.startRanging: execution throttled - nextRequest="
+ + nextRequest + ", mRttRequesterInfo=" + mRttRequesterInfo);
+ try {
+ nextRequest.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RttServiceSynchronized.startRanging: throttled, callback failed -- "
+ + e);
+ }
+ executeNextRangingRequestIfPossible(true);
+ return;
+ }
+
nextRequest.cmdId = mNextCommandId++;
if (mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) {
mRangingTimeoutMessage.schedule(
- SystemClock.elapsedRealtime() + HAL_RANGING_TIMEOUT_MS);
+ mClock.getElapsedSinceBootMillis() + HAL_RANGING_TIMEOUT_MS);
} else {
Log.w(TAG, "RttServiceSynchronized.startRanging: native rangeRequest call failed");
try {
@@ -558,6 +582,64 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
}
/**
+ * Perform pre-execution throttling checks:
+ * - If all uids in ws are in background then check last execution and block if request is
+ * more frequent than permitted
+ * - If executing (i.e. permitted) then update execution time
+ *
+ * Returns true to permit execution, false to abort it.
+ */
+ private boolean preExecThrottleCheck(WorkSource ws) {
+ if (VDBG) Log.v(TAG, "preExecThrottleCheck: ws=" + ws);
+
+ // are all UIDs running in the background or is at least 1 in the foreground?
+ boolean allUidsInBackground = true;
+ for (int i = 0; i < ws.size(); ++i) {
+ int uidImportance = mActivityManager.getUidImportance(ws.get(i));
+ if (VDBG) {
+ Log.v(TAG, "preExecThrottleCheck: uid=" + ws.get(i) + " -> importance="
+ + uidImportance);
+ }
+ if (uidImportance
+ <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) {
+ allUidsInBackground = false;
+ break;
+ }
+ }
+
+ // if all UIDs are in background then check timestamp since last execution and see if
+ // any is permitted (infrequent enough)
+ boolean allowExecution = false;
+ long mostRecentExecutionPermitted =
+ mClock.getElapsedSinceBootMillis() - BACKGROUND_PROCESS_EXEC_GAP_MS;
+ if (allUidsInBackground) {
+ for (int i = 0; i < ws.size(); ++i) {
+ RttRequesterInfo info = mRttRequesterInfo.get(ws.get(i));
+ if (info == null || info.lastRangingExecuted < mostRecentExecutionPermitted) {
+ allowExecution = true;
+ break;
+ }
+ }
+ } else {
+ allowExecution = true;
+ }
+
+ // update exec time
+ if (allowExecution) {
+ for (int i = 0; i < ws.size(); ++i) {
+ RttRequesterInfo info = mRttRequesterInfo.get(ws.get(i));
+ if (info == null) {
+ info = new RttRequesterInfo();
+ mRttRequesterInfo.put(ws.get(i), info);
+ }
+ info.lastRangingExecuted = mClock.getElapsedSinceBootMillis();
+ }
+ }
+
+ return allowExecution;
+ }
+
+ /**
* Check request for any PeerHandle Aware requests. If there are any: issue requests to
* translate the peer ID to a MAC address and abort current execution of the range request.
* The request will be re-attempted when response is received.
@@ -754,6 +836,7 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
// dump call (asynchronous most likely)
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(" mNextCommandId: " + mNextCommandId);
+ pw.println(" mRttRequesterInfo: " + mRttRequesterInfo);
pw.println(" mRttRequestQueue: " + mRttRequestQueue);
pw.println(" mRangingTimeoutMessage: " + mRangingTimeoutMessage);
mRttNative.dump(fd, pw, args);
@@ -783,4 +866,14 @@ public class RttServiceImpl extends IWifiRttManager.Stub {
", peerHandlesTranslated=").append(peerHandlesTranslated).toString();
}
}
+
+ private static class RttRequesterInfo {
+ public long lastRangingExecuted;
+
+ @Override
+ public String toString() {
+ return new StringBuilder("RttRequesterInfo: lastRangingExecuted=").append(
+ lastRangingExecuted).toString();
+ }
+ }
}
diff --git a/com/android/server/wm/AccessibilityController.java b/com/android/server/wm/AccessibilityController.java
index de4fd7cd..95b139ad 100644
--- a/com/android/server/wm/AccessibilityController.java
+++ b/com/android/server/wm/AccessibilityController.java
@@ -55,14 +55,14 @@ import android.view.SurfaceControl;
import android.view.ViewConfiguration;
import android.view.WindowInfo;
import android.view.WindowManager;
-import android.view.WindowManagerInternal.MagnificationCallbacks;
-import android.view.WindowManagerInternal.WindowsForAccessibilityCallback;
-import android.view.WindowManagerPolicy;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.android.internal.R;
import com.android.internal.os.SomeArgs;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
+import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
import java.util.ArrayList;
import java.util.HashSet;
@@ -292,6 +292,8 @@ final class AccessibilityController {
public void setMagnificationSpecLocked(MagnificationSpec spec) {
mMagnifedViewport.updateMagnificationSpecLocked(spec);
mMagnifedViewport.recomputeBoundsLocked();
+
+ mService.applyMagnificationSpec(spec);
mService.scheduleAnimationLocked();
}
@@ -421,7 +423,7 @@ final class AccessibilityController {
public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
if (spec != null && !spec.isNop()) {
- if (!mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+ if (!windowState.shouldMagnify()) {
return null;
}
}
@@ -476,6 +478,7 @@ final class AccessibilityController {
private final ViewportWindow mWindow;
private boolean mFullRedrawNeeded;
+ private int mTempLayer = 0;
public MagnifiedViewport() {
mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
@@ -565,7 +568,7 @@ final class AccessibilityController {
portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
- if (mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+ if (windowState.shouldMagnify()) {
mMagnificationRegion.op(windowBounds, Region.Op.UNION);
mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
} else {
@@ -676,10 +679,12 @@ final class AccessibilityController {
private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
final DisplayContent dc = mService.getDefaultDisplayContentLocked();
+ mTempLayer = 0;
dc.forAllWindows((w) -> {
if (w.isOnScreen() && w.isVisibleLw()
&& !w.mWinAnimator.mEnterAnimationPending) {
- outWindows.put(w.mLayer, w);
+ mTempLayer++;
+ outWindows.put(mTempLayer, w);
}
}, false /* traverseTopToBottom */ );
}
@@ -705,7 +710,7 @@ final class AccessibilityController {
SurfaceControl surfaceControl = null;
try {
mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
- surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+ surfaceControl = mService.getDefaultDisplayContentLocked().makeOverlay()
.setName(SURFACE_TITLE)
.setSize(mTempPoint.x, mTempPoint.y) // not a typo
.setFormat(PixelFormat.TRANSLUCENT)
@@ -714,8 +719,6 @@ final class AccessibilityController {
/* ignore */
}
mSurfaceControl = surfaceControl;
- mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay()
- .getLayerStack());
mSurfaceControl.setLayer(mService.mPolicy.getWindowLayerFromTypeLw(
TYPE_MAGNIFICATION_OVERLAY)
* WindowManagerService.TYPE_LAYER_MULTIPLIER);
@@ -1005,6 +1008,8 @@ final class AccessibilityController {
private final long mRecurringAccessibilityEventsIntervalMillis;
+ private int mTempLayer = 0;
+
public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
WindowsForAccessibilityCallback callback) {
mContext = windowManagerService.mContext;
@@ -1090,6 +1095,7 @@ final class AccessibilityController {
if (isReportedWindowType(windowState.mAttrs.type)) {
// Add the window to the ones to be reported.
WindowInfo window = obtainPopulatedWindowInfo(windowState, boundsInScreen);
+ window.layer = addedWindows.size();
addedWindows.add(window.token);
windows.add(window);
if (windowState.isFocused()) {
@@ -1323,9 +1329,10 @@ final class AccessibilityController {
private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
final DisplayContent dc = mService.getDefaultDisplayContentLocked();
+ mTempLayer = 0;
dc.forAllWindows((w) -> {
if (w.isVisibleLw()) {
- outWindows.put(w.mLayer, w);
+ outWindows.put(mTempLayer++, w);
}
}, false /* traverseTopToBottom */ );
}
diff --git a/com/android/server/wm/AppTransition.java b/com/android/server/wm/AppTransition.java
index c19ede0c..c2cbced2 100644
--- a/com/android/server/wm/AppTransition.java
+++ b/com/android/server/wm/AppTransition.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.view.WindowManagerInternal.AppTransitionListener;
import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation;
@@ -44,6 +43,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
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.WindowManagerInternal.AppTransitionListener;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
diff --git a/com/android/server/wm/AppWindowAnimator.java b/com/android/server/wm/AppWindowAnimator.java
index 2ef7f253..5c1d5b25 100644
--- a/com/android/server/wm/AppWindowAnimator.java
+++ b/com/android/server/wm/AppWindowAnimator.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
@@ -253,7 +253,6 @@ public class AppWindowAnimator {
private void updateLayers() {
mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
- updateThumbnailLayer();
}
private void stepThumbnailAnimation(long currentTime) {
@@ -283,27 +282,12 @@ public class AppWindowAnimator {
+ "][" + tmpFloats[Matrix.MSKEW_X]
+ "," + tmpFloats[Matrix.MSCALE_Y] + "]");
thumbnail.setAlpha(thumbnailTransformation.getAlpha());
- updateThumbnailLayer();
thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
}
/**
- * Updates the thumbnail layer z order to just above the highest animation layer if changed
- */
- void updateThumbnailLayer() {
- if (thumbnail != null) {
- final int layer = mAppToken.getHighestAnimLayer();
- if (DEBUG_LAYERS) Slog.v(TAG,
- "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
- thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
- - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
- mThumbnailLayer = layer;
- }
- }
-
- /**
* Sometimes we need to synchronize the first frame of animation with some external event, e.g.
* Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
* and keep producing the first frame of the animation.
diff --git a/com/android/server/wm/AppWindowContainerController.java b/com/android/server/wm/AppWindowContainerController.java
index 58418402..00a0d3d1 100644
--- a/com/android/server/wm/AppWindowContainerController.java
+++ b/com/android/server/wm/AppWindowContainerController.java
@@ -45,10 +45,11 @@ import android.os.Trace;
import android.util.Slog;
import android.view.DisplayInfo;
import android.view.IApplicationToken;
-import android.view.WindowManagerPolicy.StartingSurface;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.AttributeCache;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
+
/**
* Controller for the app window token container. This is created by activity manager to link
* activity records to the app window token container they use in window manager.
@@ -180,13 +181,12 @@ public class AppWindowContainerController
IApplicationToken token, AppWindowContainerListener listener, int index,
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
- int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
- Rect bounds) {
+ int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos) {
this(taskController, token, listener, index, requestedOrientation, fullscreen,
showForAllUsers,
configChanges, voiceInteraction, launchTaskBehind, alwaysFocusable,
targetSdkVersion, rotationAnimationHint, inputDispatchingTimeoutNanos,
- WindowManagerService.getInstance(), bounds);
+ WindowManagerService.getInstance());
}
public AppWindowContainerController(TaskWindowContainerController taskController,
@@ -194,7 +194,7 @@ public class AppWindowContainerController
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
- WindowManagerService service, Rect bounds) {
+ WindowManagerService service) {
super(listener, service);
mHandler = new H(service.mH.getLooper());
mToken = token;
@@ -215,7 +215,7 @@ public class AppWindowContainerController
atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
- alwaysFocusable, this, bounds);
+ alwaysFocusable, this);
if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
+ " controller=" + taskController + " at " + index);
task.addChild(atoken, index);
@@ -227,11 +227,11 @@ public class AppWindowContainerController
boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
- boolean alwaysFocusable, AppWindowContainerController controller, Rect bounds) {
+ boolean alwaysFocusable, AppWindowContainerController controller) {
return new AppWindowToken(service, token, voiceInteraction, dc,
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
- controller, bounds);
+ controller);
}
public void removeContainer(int displayId) {
@@ -298,17 +298,6 @@ public class AppWindowContainerController
}
}
- // TODO(b/36505427): Maybe move to WindowContainerController so other sub-classes can use it as
- // a generic way to set override config. Need to untangle current ways the override config is
- // currently set for tasks and displays before we are doing that though.
- public void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
- synchronized(mWindowMap) {
- if (mContainer != null) {
- mContainer.onOverrideConfigurationChanged(overrideConfiguration, bounds);
- }
- }
- }
-
public void setDisablePreviewScreenshots(boolean disable) {
synchronized (mWindowMap) {
if (mContainer == null) {
diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java
index 98db80ef..c39ce982 100644
--- a/com/android/server/wm/AppWindowToken.java
+++ b/com/android/server/wm/AppWindowToken.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
@@ -28,8 +27,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
@@ -67,10 +66,10 @@ import android.view.IApplicationToken;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerPolicy.StartingSurface;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.input.InputApplicationHandle;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
@@ -176,11 +175,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
private boolean mLastContainsShowWhenLockedWindow;
private boolean mLastContainsDismissKeyguardWindow;
- // The bounds of this activity. Mainly used for aspect-ratio compatibility.
- // TODO(b/36505427): Every level on WindowContainer now has bounds information, which directly
- // affects the configuration. We should probably move this into that class.
- private final Rect mBounds = new Rect();
-
ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
@@ -197,8 +191,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
int configChanges, boolean launchTaskBehind, boolean alwaysFocusable,
- AppWindowContainerController controller, Rect bounds) {
- this(service, token, voiceInteraction, dc, fullscreen, bounds);
+ AppWindowContainerController controller) {
+ this(service, token, voiceInteraction, dc, fullscreen);
setController(controller);
mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
mShowForAllUsers = showForAllUsers;
@@ -215,7 +209,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
}
AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
- DisplayContent dc, boolean fillsParent, Rect bounds) {
+ DisplayContent dc, boolean fillsParent) {
super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, dc,
false /* ownerCanManageAppTokens */);
appToken = token;
@@ -223,27 +217,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
mFillsParent = fillsParent;
mInputApplicationHandle = new InputApplicationHandle(this);
mAppAnimator = new AppWindowAnimator(this, service);
- if (bounds != null) {
- mBounds.set(bounds);
- }
- }
-
- void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
- onOverrideConfigurationChanged(overrideConfiguration);
- if (mBounds.equals(bounds)) {
- return;
- }
- // TODO(b/36505427): If bounds is in WC, then we can automatically call onResize() when set.
- mBounds.set(bounds);
- onResize();
- }
-
- void getBounds(Rect outBounds) {
- outBounds.set(mBounds);
- }
-
- boolean hasBounds() {
- return !mBounds.isEmpty();
}
void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
diff --git a/com/android/server/wm/BlackFrame.java b/com/android/server/wm/BlackFrame.java
index 9729e501..f19cd0ff 100644
--- a/com/android/server/wm/BlackFrame.java
+++ b/com/android/server/wm/BlackFrame.java
@@ -30,7 +30,6 @@ import android.graphics.Rect;
import android.util.Slog;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
/**
* Four black surfaces put together to make a black frame.
@@ -42,22 +41,22 @@ public class BlackFrame {
final int layer;
final SurfaceControl surface;
- BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b, int layerStack)
- throws OutOfResourcesException {
+ BlackSurface(int layer,
+ int l, int t, int r, int b, DisplayContent dc) throws OutOfResourcesException {
left = l;
top = t;
this.layer = layer;
int w = r-l;
int h = b-t;
- surface = new SurfaceControl.Builder(session)
+ surface = dc.makeOverlay()
.setName("BlackSurface")
.setSize(w, h)
.setColorLayer(true)
+ .setParent(null) // TODO: Work-around for b/69259549
.build();
surface.setAlpha(1);
- surface.setLayerStack(layerStack);
surface.setLayer(layer);
surface.show();
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
@@ -114,30 +113,32 @@ public class BlackFrame {
}
}
- public BlackFrame(SurfaceSession session, Rect outer, Rect inner, int layer, int layerStack,
+ public BlackFrame(Rect outer, Rect inner, int layer, DisplayContent dc,
boolean forceDefaultOrientation) throws OutOfResourcesException {
boolean success = false;
mForceDefaultOrientation = forceDefaultOrientation;
+ // TODO: Why do we use 4 surfaces instead of just one big one behind the screenshot?
+ // b/68253229
mOuterRect = new Rect(outer);
mInnerRect = new Rect(inner);
try {
if (outer.top < inner.top) {
- mBlackSurfaces[0] = new BlackSurface(session, layer,
- outer.left, outer.top, inner.right, inner.top, layerStack);
+ mBlackSurfaces[0] = new BlackSurface(layer,
+ outer.left, outer.top, inner.right, inner.top, dc);
}
if (outer.left < inner.left) {
- mBlackSurfaces[1] = new BlackSurface(session, layer,
- outer.left, inner.top, inner.left, outer.bottom, layerStack);
+ mBlackSurfaces[1] = new BlackSurface(layer,
+ outer.left, inner.top, inner.left, outer.bottom, dc);
}
if (outer.bottom > inner.bottom) {
- mBlackSurfaces[2] = new BlackSurface(session, layer,
- inner.left, inner.bottom, outer.right, outer.bottom, layerStack);
+ mBlackSurfaces[2] = new BlackSurface(layer,
+ inner.left, inner.bottom, outer.right, outer.bottom, dc);
}
if (outer.right > inner.right) {
- mBlackSurfaces[3] = new BlackSurface(session, layer,
- inner.right, outer.top, outer.right, inner.bottom, layerStack);
+ mBlackSurfaces[3] = new BlackSurface(layer,
+ inner.right, outer.top, outer.right, inner.bottom, dc);
}
success = true;
} finally {
diff --git a/com/android/server/wm/BoundsAnimationController.java b/com/android/server/wm/BoundsAnimationController.java
index 7953ee43..ba67ff6a 100644
--- a/com/android/server/wm/BoundsAnimationController.java
+++ b/com/android/server/wm/BoundsAnimationController.java
@@ -33,7 +33,6 @@ import android.util.ArrayMap;
import android.util.Slog;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
-import android.view.WindowManagerInternal;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/com/android/server/wm/CircularDisplayMask.java b/com/android/server/wm/CircularDisplayMask.java
index 2d5d1b2f..2a216abb 100644
--- a/com/android/server/wm/CircularDisplayMask.java
+++ b/com/android/server/wm/CircularDisplayMask.java
@@ -33,7 +33,6 @@ import android.view.Display;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
class CircularDisplayMask {
private static final String TAG = TAG_WITH_CLASS_NAME ? "CircularDisplayMask" : TAG_WM;
@@ -54,8 +53,10 @@ class CircularDisplayMask {
private boolean mDimensionsUnequal = false;
private int mMaskThickness;
- public CircularDisplayMask(Display display, SurfaceSession session, int zOrder,
+ public CircularDisplayMask(DisplayContent dc, int zOrder,
int screenOffset, int maskThickness) {
+ final Display display = dc.getDisplay();
+
mScreenSize = new Point();
display.getSize(mScreenSize);
if (mScreenSize.x != mScreenSize.y + screenOffset) {
@@ -66,7 +67,7 @@ class CircularDisplayMask {
SurfaceControl ctrl = null;
try {
- ctrl = new SurfaceControl.Builder(session)
+ ctrl = dc.makeOverlay()
.setName("CircularDisplayMask")
.setSize(mScreenSize.x, mScreenSize.y) // not a typo
.setFormat(PixelFormat.TRANSLUCENT)
diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java
index cc948070..d340923b 100644
--- a/com/android/server/wm/ConfigurationContainer.java
+++ b/com/android/server/wm/ConfigurationContainer.java
@@ -36,6 +36,7 @@ import static com.android.server.wm.proto.ConfigurationContainerProto.OVERRIDE_C
import android.annotation.CallSuper;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
@@ -46,6 +47,11 @@ import java.util.ArrayList;
* hierarchy.
*/
public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
+ /**
+ * {@link #Rect} returned from {@link #getOverrideBounds()} to prevent original value from being
+ * set directly.
+ */
+ private Rect mReturnBounds = new Rect();
/** Contains override configuration settings applied to this configuration container. */
private Configuration mOverrideConfiguration = new Configuration();
@@ -71,6 +77,16 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
// TODO: Can't have ag/2592611 soon enough!
private final Configuration mTmpConfig = new Configuration();
+ // Used for setting bounds
+ private final Rect mTmpRect = new Rect();
+
+ static final int BOUNDS_CHANGE_NONE = 0;
+ // Return value from {@link setBounds} indicating the position of the override bounds changed.
+ static final int BOUNDS_CHANGE_POSITION = 1;
+ // Return value from {@link setBounds} indicating the size of the override bounds changed.
+ static final int BOUNDS_CHANGE_SIZE = 1 << 1;
+
+
/**
* Returns full configuration applied to this configuration container.
* This method should be used for getting settings applied in each particular level of the
@@ -148,6 +164,118 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
}
}
+ /**
+ * Indicates whether this container has not specified any bounds different from its parent. In
+ * this case, it will inherit the bounds of the first ancestor which specifies a bounds.
+ * @return {@code true} if no explicit bounds have been set at this container level.
+ * {@code false} otherwise.
+ */
+ public boolean matchParentBounds() {
+ return getOverrideBounds().isEmpty();
+ }
+
+ /**
+ * Returns whether the bounds specified is considered the same as the existing override bounds.
+ * This is either when the two bounds are equal or the override bounds is empty and the
+ * specified bounds is null.
+ *
+ * @return {@code true} if the bounds are equivalent, {@code false} otherwise
+ */
+ public boolean equivalentOverrideBounds(Rect bounds) {
+ return equivalentBounds(getOverrideBounds(), bounds);
+ }
+
+ /**
+ * Returns whether the two bounds are equal to each other or are a combination of null or empty.
+ */
+ public static boolean equivalentBounds(Rect bounds, Rect other) {
+ return bounds == other
+ || (bounds != null && (bounds.equals(other) || (bounds.isEmpty() && other == null)))
+ || (other != null && other.isEmpty() && bounds == null);
+ }
+
+ /**
+ * Returns the effective bounds of this container, inheriting the first non-empty bounds set in
+ * its ancestral hierarchy, including itself.
+ * @return
+ */
+ public Rect getBounds() {
+ mReturnBounds.set(getConfiguration().windowConfiguration.getBounds());
+ return mReturnBounds;
+ }
+
+ public void getBounds(Rect outBounds) {
+ outBounds.set(getBounds());
+ }
+
+ /**
+ * Returns the current bounds explicitly set on this container. The {@link Rect} handed back is
+ * shared for all calls to this method and should not be modified.
+ */
+ public Rect getOverrideBounds() {
+ mReturnBounds.set(getOverrideConfiguration().windowConfiguration.getBounds());
+
+ return mReturnBounds;
+ }
+
+ /**
+ * Sets the passed in {@link Rect} to the current bounds.
+ * @see {@link #getOverrideBounds()}.
+ */
+ public void getOverrideBounds(Rect outBounds) {
+ outBounds.set(getOverrideBounds());
+ }
+
+ /**
+ * Sets the bounds at the current hierarchy level, overriding any bounds set on an ancestor.
+ * This value will be reported when {@link #getBounds()} and {@link #getOverrideBounds()}. If
+ * an empty {@link Rect} or null is specified, this container will be considered to match its
+ * parent bounds {@see #matchParentBounds} and will inherit bounds from its parent.
+ * @param bounds The bounds defining the container size.
+ * @return a bitmask representing the types of changes made to the bounds.
+ */
+ public int setBounds(Rect bounds) {
+ int boundsChange = diffOverrideBounds(bounds);
+
+ if (boundsChange == BOUNDS_CHANGE_NONE) {
+ return boundsChange;
+ }
+
+
+ mTmpConfig.setTo(getOverrideConfiguration());
+ mTmpConfig.windowConfiguration.setBounds(bounds);
+ onOverrideConfigurationChanged(mTmpConfig);
+
+ return boundsChange;
+ }
+
+ public int setBounds(int left, int top, int right, int bottom) {
+ mTmpRect.set(left, top, right, bottom);
+ return setBounds(mTmpRect);
+ }
+
+ int diffOverrideBounds(Rect bounds) {
+ if (equivalentOverrideBounds(bounds)) {
+ return BOUNDS_CHANGE_NONE;
+ }
+
+ int boundsChange = BOUNDS_CHANGE_NONE;
+
+ final Rect existingBounds = getOverrideBounds();
+
+ if (bounds == null || existingBounds.left != bounds.left
+ || existingBounds.top != bounds.top) {
+ boundsChange |= BOUNDS_CHANGE_POSITION;
+ }
+
+ if (bounds == null || existingBounds.width() != bounds.width()
+ || existingBounds.height() != bounds.height()) {
+ boundsChange |= BOUNDS_CHANGE_SIZE;
+ }
+
+ return boundsChange;
+ }
+
public WindowConfiguration getWindowConfiguration() {
return mFullConfiguration.windowConfiguration;
}
@@ -375,6 +503,10 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
return toString();
}
+ boolean isAlwaysOnTop() {
+ return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
+ }
+
abstract protected int getChildCount();
abstract protected E getChildAt(int index);
diff --git a/com/android/server/wm/DimLayer.java b/com/android/server/wm/DimLayer.java
deleted file mode 100644
index 8fb2be8c..00000000
--- a/com/android/server/wm/DimLayer.java
+++ /dev/null
@@ -1,380 +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.server.wm;
-
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-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;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.util.Slog;
-import android.view.DisplayInfo;
-import android.view.SurfaceControl;
-
-import java.io.PrintWriter;
-
-public class DimLayer {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "DimLayer" : TAG_WM;
- private final WindowManagerService mService;
-
- /** Actual surface that dims */
- private SurfaceControl mDimSurface;
-
- /** Last value passed to mDimSurface.setAlpha() */
- private float mAlpha = 0;
-
- /** Last value passed to mDimSurface.setLayer() */
- private int mLayer = -1;
-
- /** Next values to pass to mDimSurface.setPosition() and mDimSurface.setSize() */
- private final Rect mBounds = new Rect();
-
- /** Last values passed to mDimSurface.setPosition() and mDimSurface.setSize() */
- private final Rect mLastBounds = new Rect();
-
- /** True after mDimSurface.show() has been called, false after mDimSurface.hide(). */
- private boolean mShowing = false;
-
- /** Value of mAlpha when beginning transition to mTargetAlpha */
- private float mStartAlpha = 0;
-
- /** Final value of mAlpha following transition */
- private float mTargetAlpha = 0;
-
- /** Time in units of SystemClock.uptimeMillis() at which the current transition started */
- private long mStartTime;
-
- /** Time in milliseconds to take to transition from mStartAlpha to mTargetAlpha */
- private long mDuration;
-
- private boolean mDestroyed = false;
-
- private final int mDisplayId;
-
-
- /** Interface implemented by users of the dim layer */
- interface DimLayerUser {
- /** Returns true if the dim should be fullscreen. */
- boolean dimFullscreen();
- /** Returns the display info. of the dim layer user. */
- DisplayInfo getDisplayInfo();
- /** Returns true if the dim layer user is currently attached to a display */
- boolean isAttachedToDisplay();
- /** Gets the bounds of the dim layer user. */
- void getDimBounds(Rect outBounds);
- /** Returns the layer to place a dim layer. */
- default int getLayerForDim(WindowStateAnimator animator, int layerOffset,
- int defaultLayer) {
- return defaultLayer;
- }
-
- String toShortString();
- }
- /** The user of this dim layer. */
- private final DimLayerUser mUser;
-
- private final String mName;
-
- DimLayer(WindowManagerService service, DimLayerUser user, int displayId, String name) {
- mUser = user;
- mDisplayId = displayId;
- mService = service;
- mName = name;
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "Ctor: displayId=" + displayId);
- }
-
- private void constructSurface(WindowManagerService service) {
- service.openSurfaceTransaction();
- try {
- mDimSurface = new SurfaceControl.Builder(service.mFxSession)
- .setName(mName)
- .setSize(16, 16)
- .setColorLayer(true)
- .build();
-
- if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
- " DIM " + mDimSurface + ": CREATE");
- mDimSurface.setLayerStack(mDisplayId);
- adjustBounds();
- adjustAlpha(mAlpha);
- adjustLayer(mLayer);
- } catch (Exception e) {
- Slog.e(TAG_WM, "Exception creating Dim surface", e);
- } finally {
- service.closeSurfaceTransaction("DimLayer.constructSurface");
- }
- }
-
- /** Return true if dim layer is showing */
- boolean isDimming() {
- return mTargetAlpha != 0;
- }
-
- /** Return true if in a transition period */
- boolean isAnimating() {
- return mTargetAlpha != mAlpha;
- }
-
- float getTargetAlpha() {
- return mTargetAlpha;
- }
-
- void setLayer(int layer) {
- if (mLayer == layer) {
- return;
- }
- mLayer = layer;
- adjustLayer(layer);
- }
-
- private void adjustLayer(int layer) {
- if (mDimSurface != null) {
- mDimSurface.setLayer(layer);
- }
- }
-
- int getLayer() {
- return mLayer;
- }
-
- private void setAlpha(float alpha) {
- if (mAlpha == alpha) {
- return;
- }
- mAlpha = alpha;
- adjustAlpha(alpha);
- }
-
- private void adjustAlpha(float alpha) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha alpha=" + alpha);
- try {
- if (mDimSurface != null) {
- mDimSurface.setAlpha(alpha);
- }
- if (alpha == 0 && mShowing) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha hiding");
- if (mDimSurface != null) {
- mDimSurface.hide();
- mShowing = false;
- }
- } else if (alpha > 0 && !mShowing) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha showing");
- if (mDimSurface != null) {
- mDimSurface.show();
- mShowing = true;
- }
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Failure setting alpha immediately", e);
- }
- }
-
- /**
- * NOTE: Must be called with Surface transaction open.
- */
- private void adjustBounds() {
- if (mUser.dimFullscreen()) {
- getBoundsForFullscreen(mBounds);
- }
-
- if (mDimSurface != null) {
- mDimSurface.setPosition(mBounds.left, mBounds.top);
- mDimSurface.setSize(mBounds.width(), mBounds.height());
- if (DEBUG_DIM_LAYER) Slog.v(TAG,
- "adjustBounds user=" + mUser.toShortString() + " mBounds=" + mBounds);
- }
-
- mLastBounds.set(mBounds);
- }
-
- private void getBoundsForFullscreen(Rect outBounds) {
- final int dw, dh;
- final float xPos, yPos;
- // Set surface size to screen size.
- final DisplayInfo info = mUser.getDisplayInfo();
- // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose
- // a corner.
- dw = (int) (info.logicalWidth * 1.5);
- dh = (int) (info.logicalHeight * 1.5);
- // back off position so 1/4 of Surface is before and 1/4 is after.
- xPos = -1 * dw / 6;
- yPos = -1 * dh / 6;
- outBounds.set((int) xPos, (int) yPos, (int) xPos + dw, (int) yPos + dh);
- }
-
- void setBoundsForFullscreen() {
- getBoundsForFullscreen(mBounds);
- setBounds(mBounds);
- }
-
- /** @param bounds The new bounds to set */
- void setBounds(Rect bounds) {
- mBounds.set(bounds);
- if (isDimming() && !mLastBounds.equals(bounds)) {
- try {
- mService.openSurfaceTransaction();
- adjustBounds();
- } catch (RuntimeException e) {
- Slog.w(TAG, "Failure setting size", e);
- } finally {
- mService.closeSurfaceTransaction("DimLayer.setBounds");
- }
- }
- }
-
- /**
- * @param duration The time to test.
- * @return True if the duration would lead to an earlier end to the current animation.
- */
- private boolean durationEndsEarlier(long duration) {
- return SystemClock.uptimeMillis() + duration < mStartTime + mDuration;
- }
-
- /** Jump to the end of the animation.
- * NOTE: Must be called with Surface transaction open. */
- void show() {
- if (isAnimating()) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: immediate");
- show(mLayer, mTargetAlpha, 0);
- }
- }
-
- /**
- * Begin an animation to a new dim value.
- * NOTE: Must be called with Surface transaction open.
- *
- * @param layer The layer to set the surface to.
- * @param alpha The dim value to end at.
- * @param duration How long to take to get there in milliseconds.
- */
- void show(int layer, float alpha, long duration) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: layer=" + layer + " alpha=" + alpha
- + " duration=" + duration + ", mDestroyed=" + mDestroyed);
- if (mDestroyed) {
- Slog.e(TAG, "show: no Surface");
- // Make sure isAnimating() returns false.
- mTargetAlpha = mAlpha = 0;
- return;
- }
-
- if (mDimSurface == null) {
- constructSurface(mService);
- }
-
- if (!mLastBounds.equals(mBounds)) {
- adjustBounds();
- }
- setLayer(layer);
-
- long curTime = SystemClock.uptimeMillis();
- final boolean animating = isAnimating();
- if ((animating && (mTargetAlpha != alpha || durationEndsEarlier(duration)))
- || (!animating && mAlpha != alpha)) {
- if (duration <= 0) {
- // No animation required, just set values.
- setAlpha(alpha);
- } else {
- // Start or continue animation with new parameters.
- mStartAlpha = mAlpha;
- mStartTime = curTime;
- mDuration = duration;
- }
- }
- mTargetAlpha = alpha;
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: mStartAlpha=" + mStartAlpha + " mStartTime="
- + mStartTime + " mTargetAlpha=" + mTargetAlpha);
- }
-
- /** Immediate hide.
- * NOTE: Must be called with Surface transaction open. */
- void hide() {
- if (mShowing) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: immediate");
- hide(0);
- }
- }
-
- /**
- * Gradually fade to transparent.
- * NOTE: Must be called with Surface transaction open.
- *
- * @param duration Time to fade in milliseconds.
- */
- void hide(long duration) {
- if (mShowing && (mTargetAlpha != 0 || durationEndsEarlier(duration))) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: duration=" + duration);
- show(mLayer, 0, duration);
- }
- }
-
- /**
- * Advance the dimming per the last #show(int, float, long) call.
- * NOTE: Must be called with Surface transaction open.
- *
- * @return True if animation is still required after this step.
- */
- boolean stepAnimation() {
- if (mDestroyed) {
- Slog.e(TAG, "stepAnimation: surface destroyed");
- // Ensure that isAnimating() returns false;
- mTargetAlpha = mAlpha = 0;
- return false;
- }
- if (isAnimating()) {
- final long curTime = SystemClock.uptimeMillis();
- final float alphaDelta = mTargetAlpha - mStartAlpha;
- float alpha = mStartAlpha + alphaDelta * (curTime - mStartTime) / mDuration;
- if (alphaDelta > 0 && alpha > mTargetAlpha ||
- alphaDelta < 0 && alpha < mTargetAlpha) {
- // Don't exceed limits.
- alpha = mTargetAlpha;
- }
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "stepAnimation: curTime=" + curTime + " alpha=" + alpha);
- setAlpha(alpha);
- }
-
- return isAnimating();
- }
-
- /** Cleanup */
- void destroySurface() {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "destroySurface.");
- if (mDimSurface != null) {
- mDimSurface.destroy();
- mDimSurface = null;
- }
- mDestroyed = true;
- }
-
- public void printTo(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("mDimSurface="); pw.print(mDimSurface);
- pw.print(" mLayer="); pw.print(mLayer);
- pw.print(" mAlpha="); pw.println(mAlpha);
- pw.print(prefix); pw.print("mLastBounds="); pw.print(mLastBounds.toShortString());
- pw.print(" mBounds="); pw.println(mBounds.toShortString());
- pw.print(prefix); pw.print("Last animation: ");
- pw.print(" mDuration="); pw.print(mDuration);
- pw.print(" mStartTime="); pw.print(mStartTime);
- pw.print(" curTime="); pw.println(SystemClock.uptimeMillis());
- pw.print(prefix); pw.print(" mStartAlpha="); pw.print(mStartAlpha);
- pw.print(" mTargetAlpha="); pw.println(mTargetAlpha);
- }
-}
diff --git a/com/android/server/wm/DimLayerController.java b/com/android/server/wm/DimLayerController.java
deleted file mode 100644
index 6f9e45a6..00000000
--- a/com/android/server/wm/DimLayerController.java
+++ /dev/null
@@ -1,403 +0,0 @@
-package com.android.server.wm;
-
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-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.LAYER_OFFSET_DIM;
-
-import android.graphics.Rect;
-import android.util.ArrayMap;
-import android.util.Slog;
-import android.util.TypedValue;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.DimLayer.DimLayerUser;
-
-import java.io.PrintWriter;
-
-/**
- * Centralizes the control of dim layers used for
- * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
- * as well as other use cases (such as dimming above a dead window).
- */
-class DimLayerController {
- private static final String TAG_LOCAL = "DimLayerController";
- private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
-
- /** Amount of time in milliseconds to animate the dim surface from one value to another,
- * when no window animation is driving it. */
- private static final int DEFAULT_DIM_DURATION = 200;
-
- /**
- * The default amount of dim applied over a dead window
- */
- private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
-
- // Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this
- // instead of creating a new object per fullscreen task on a display.
- private DimLayer mSharedFullScreenDimLayer;
-
- private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>();
-
- private DisplayContent mDisplayContent;
-
- private Rect mTmpBounds = new Rect();
-
- DimLayerController(DisplayContent displayContent) {
- mDisplayContent = displayContent;
- }
-
- /** Updates the dim layer bounds, recreating it if needed. */
- void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
- final DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
- final boolean previousFullscreen = state.dimLayer != null
- && state.dimLayer == mSharedFullScreenDimLayer;
- DimLayer newDimLayer;
- final int displayId = mDisplayContent.getDisplayId();
- if (dimLayerUser.dimFullscreen()) {
- if (previousFullscreen && mSharedFullScreenDimLayer != null) {
- // Update the bounds for fullscreen in case of rotation.
- mSharedFullScreenDimLayer.setBoundsForFullscreen();
- return;
- }
- // Use shared fullscreen dim layer
- newDimLayer = mSharedFullScreenDimLayer;
- if (newDimLayer == null) {
- if (state.dimLayer != null) {
- // Re-purpose the previous dim layer.
- newDimLayer = state.dimLayer;
- } else {
- // Create new full screen dim layer.
- newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
- getDimLayerTag(dimLayerUser));
- }
- dimLayerUser.getDimBounds(mTmpBounds);
- newDimLayer.setBounds(mTmpBounds);
- mSharedFullScreenDimLayer = newDimLayer;
- } else if (state.dimLayer != null) {
- state.dimLayer.destroySurface();
- }
- } else {
- newDimLayer = (state.dimLayer == null || previousFullscreen)
- ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
- getDimLayerTag(dimLayerUser))
- : state.dimLayer;
- dimLayerUser.getDimBounds(mTmpBounds);
- newDimLayer.setBounds(mTmpBounds);
- }
- state.dimLayer = newDimLayer;
- }
-
- private static String getDimLayerTag(DimLayerUser dimLayerUser) {
- return TAG_LOCAL + "/" + dimLayerUser.toShortString();
- }
-
- private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
- + dimLayerUser.toShortString());
- DimLayerState state = mState.get(dimLayerUser);
- if (state == null) {
- state = new DimLayerState();
- mState.put(dimLayerUser, state);
- }
- return state;
- }
-
- private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
- DimLayerState state = mState.get(dimLayerUser);
- if (state == null) {
- if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: "
- + dimLayerUser.toShortString());
- return;
- }
- state.continueDimming = true;
- }
-
- boolean isDimming() {
- for (int i = mState.size() - 1; i >= 0; i--) {
- DimLayerState state = mState.valueAt(i);
- if (state.dimLayer != null && state.dimLayer.isDimming()) {
- return true;
- }
- }
- return false;
- }
-
- void resetDimming() {
- for (int i = mState.size() - 1; i >= 0; i--) {
- mState.valueAt(i).continueDimming = false;
- }
- }
-
- private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
- DimLayerState state = mState.get(dimLayerUser);
- return state != null && state.continueDimming;
- }
-
- void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
- WindowStateAnimator newWinAnimator, boolean aboveApp) {
- // Only set dim params on the highest dimmed layer.
- // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
- DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
- state.dimAbove = aboveApp;
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
- + " dimLayerUser=" + dimLayerUser.toShortString()
- + " newWinAnimator=" + newWinAnimator
- + " state.animator=" + state.animator);
- if (newWinAnimator.getShown() && (state.animator == null
- || !state.animator.getShown()
- || state.animator.mAnimLayer <= newWinAnimator.mAnimLayer)) {
- state.animator = newWinAnimator;
- if (state.animator.mWin.mAppToken == null && !dimLayerUser.dimFullscreen()) {
- // Dim should cover the entire screen for system windows.
- mDisplayContent.getLogicalDisplayRect(mTmpBounds);
- } else {
- dimLayerUser.getDimBounds(mTmpBounds);
- }
- state.dimLayer.setBounds(mTmpBounds);
- }
- }
-
- void stopDimmingIfNeeded() {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded, mState.size()=" + mState.size());
- for (int i = mState.size() - 1; i >= 0; i--) {
- DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i);
- stopDimmingIfNeeded(dimLayerUser);
- }
- }
-
- private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) {
- // No need to check if state is null, we know the key has a value.
- DimLayerState state = mState.get(dimLayerUser);
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
- + " dimLayerUser=" + dimLayerUser.toShortString()
- + " state.continueDimming=" + state.continueDimming
- + " state.dimLayer.isDimming=" + state.dimLayer.isDimming());
- if (state.animator != null && state.animator.mWin.mWillReplaceWindow) {
- return;
- }
-
- if (!state.continueDimming && state.dimLayer.isDimming()) {
- state.animator = null;
- dimLayerUser.getDimBounds(mTmpBounds);
- state.dimLayer.setBounds(mTmpBounds);
- }
- }
-
- boolean animateDimLayers() {
- int fullScreen = -1;
- int fullScreenAndDimming = -1;
- int topFullScreenUserLayer = 0;
- boolean result = false;
-
- for (int i = mState.size() - 1; i >= 0; i--) {
- final DimLayer.DimLayerUser user = mState.keyAt(i);
- final DimLayerState state = mState.valueAt(i);
-
- if (!user.isAttachedToDisplay()) {
- // Leaked dim user that is no longer attached to the display. Go ahead and clean it
- // clean-up and log what happened.
- // TODO: This is a work around for b/34395537 as the dim user should have cleaned-up
- // it self when it was detached from the display. Need to investigate how the dim
- // user is leaking...
- //Slog.wtfStack(TAG_WM, "Leaked dim user=" + user.toShortString()
- // + " state=" + state);
- Slog.w(TAG_WM, "Leaked dim user=" + user.toShortString() + " state=" + state);
- removeDimLayerUser(user);
- continue;
- }
-
- // We have to check that we are actually the shared fullscreen layer
- // for this path. If we began as non fullscreen and became fullscreen
- // (e.g. Docked stack closing), then we may not be the shared layer
- // and we have to make sure we always animate the layer.
- if (user.dimFullscreen() && state.dimLayer == mSharedFullScreenDimLayer) {
- fullScreen = i;
- if (!state.continueDimming) {
- continue;
- }
-
- // When choosing which user to assign the shared fullscreen layer to
- // we need to look at Z-order.
- if (topFullScreenUserLayer == 0 ||
- (state.animator != null && state.animator.mAnimLayer > topFullScreenUserLayer)) {
- fullScreenAndDimming = i;
- if (state.animator != null) {
- topFullScreenUserLayer = state.animator.mAnimLayer;
- }
- }
- } else {
- // We always want to animate the non fullscreen windows, they don't share their
- // dim layers.
- result |= animateDimLayers(user);
- }
- }
- // For the shared, full screen dim layer, we prefer the animation that is causing it to
- // appear.
- if (fullScreenAndDimming != -1) {
- result |= animateDimLayers(mState.keyAt(fullScreenAndDimming));
- } else if (fullScreen != -1) {
- // If there is no animation for the full screen dim layer to appear, we can use any of
- // the animators that will cause it to disappear.
- result |= animateDimLayers(mState.keyAt(fullScreen));
- }
- return result;
- }
-
- private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
- DimLayerState state = mState.get(dimLayerUser);
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
- + " dimLayerUser=" + dimLayerUser.toShortString()
- + " state.animator=" + state.animator
- + " state.continueDimming=" + state.continueDimming);
- final int dimLayer;
- final float dimAmount;
- if (state.animator == null) {
- dimLayer = state.dimLayer.getLayer();
- dimAmount = 0;
- } else {
- if (state.dimAbove) {
- dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
- dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
- } else {
- dimLayer = dimLayerUser.getLayerForDim(state.animator, LAYER_OFFSET_DIM,
- state.animator.mAnimLayer - LAYER_OFFSET_DIM);
- dimAmount = state.animator.mWin.mAttrs.dimAmount;
- }
- }
- final float targetAlpha = state.dimLayer.getTargetAlpha();
- if (targetAlpha != dimAmount) {
- if (state.animator == null) {
- state.dimLayer.hide(DEFAULT_DIM_DURATION);
- } else {
- long duration = (state.animator.mAnimating && state.animator.mAnimation != null)
- ? state.animator.mAnimation.computeDurationHint()
- : DEFAULT_DIM_DURATION;
- if (targetAlpha > dimAmount) {
- duration = getDimLayerFadeDuration(duration);
- }
- state.dimLayer.show(dimLayer, dimAmount, duration);
-
- // If we showed a dim layer, make sure to redo the layout because some things depend
- // on whether a dim layer is showing or not.
- if (targetAlpha == 0) {
- mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
- mDisplayContent.setLayoutNeeded();
- }
- }
- } else if (state.dimLayer.getLayer() != dimLayer) {
- state.dimLayer.setLayer(dimLayer);
- }
- if (state.dimLayer.isAnimating()) {
- if (!mDisplayContent.okToAnimate()) {
- // Jump to the end of the animation.
- state.dimLayer.show();
- } else {
- return state.dimLayer.stepAnimation();
- }
- }
- return false;
- }
-
- boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
- DimLayerState state = mState.get(dimLayerUser);
- return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
- }
-
- private long getDimLayerFadeDuration(long duration) {
- TypedValue tv = new TypedValue();
- mDisplayContent.mService.mContext.getResources().getValue(
- com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
- if (tv.type == TypedValue.TYPE_FRACTION) {
- duration = (long) tv.getFraction(duration, duration);
- } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
- duration = tv.data;
- }
- return duration;
- }
-
- void close() {
- for (int i = mState.size() - 1; i >= 0; i--) {
- DimLayerState state = mState.valueAt(i);
- state.dimLayer.destroySurface();
- }
- mState.clear();
- mSharedFullScreenDimLayer = null;
- }
-
- void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
- DimLayerState state = mState.get(dimLayerUser);
- if (state != null) {
- // Destroy the surface, unless it's the shared fullscreen dim.
- if (state.dimLayer != mSharedFullScreenDimLayer) {
- state.dimLayer.destroySurface();
- }
- mState.remove(dimLayerUser);
- }
- if (mState.isEmpty()) {
- mSharedFullScreenDimLayer = null;
- }
- }
-
- @VisibleForTesting
- boolean hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
- return mState.containsKey(dimLayerUser);
- }
-
- @VisibleForTesting
- boolean hasSharedFullScreenDimLayer() {
- return mSharedFullScreenDimLayer != null;
- }
-
- void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
- applyDim(dimLayerUser, animator, false /* aboveApp */);
- }
-
- void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
- applyDim(dimLayerUser, animator, true /* aboveApp */);
- }
-
- void applyDim(
- DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
- if (dimLayerUser == null) {
- Slog.e(TAG, "Trying to apply dim layer for: " + this
- + ", but no dim layer user found.");
- return;
- }
- if (!getContinueDimming(dimLayerUser)) {
- setContinueDimming(dimLayerUser);
- if (!isDimming(dimLayerUser, animator)) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
- startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
- }
- }
- }
-
- private static class DimLayerState {
- // The particular window requesting a dim layer. If null, hide dimLayer.
- WindowStateAnimator animator;
- // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
- // end then stop any dimming.
- boolean continueDimming;
- DimLayer dimLayer;
- boolean dimAbove;
- }
-
- void dump(String prefix, PrintWriter pw) {
- pw.println(prefix + "DimLayerController");
- final String doubleSpace = " ";
- final String prefixPlusDoubleSpace = prefix + doubleSpace;
-
- for (int i = 0, n = mState.size(); i < n; i++) {
- pw.println(prefixPlusDoubleSpace + mState.keyAt(i).toShortString());
- DimLayerState state = mState.valueAt(i);
- pw.println(prefixPlusDoubleSpace + doubleSpace + "dimLayer="
- + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" : state.dimLayer)
- + ", animator=" + state.animator + ", continueDimming=" + state.continueDimming);
- if (state.dimLayer != null) {
- state.dimLayer.printTo(prefixPlusDoubleSpace + doubleSpace, pw);
- }
- }
- }
-}
diff --git a/com/android/server/wm/Dimmer.java b/com/android/server/wm/Dimmer.java
new file mode 100644
index 00000000..9fe16ae8
--- /dev/null
+++ b/com/android/server/wm/Dimmer.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wm;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.graphics.Rect;
+
+/**
+ * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
+ * black layers of varying opacity at various Z-levels which create the effect of a Dim.
+ */
+class Dimmer {
+ private static final String TAG = "WindowManager";
+
+ private class DimState {
+ SurfaceControl mSurfaceControl;
+ boolean mDimming;
+
+ /**
+ * Used for Dims not assosciated with a WindowContainer. See {@link Dimmer#dimAbove} for
+ * details on Dim lifecycle.
+ */
+ boolean mDontReset;
+
+ DimState(SurfaceControl ctl) {
+ mSurfaceControl = ctl;
+ mDimming = true;
+ }
+ };
+
+ private ArrayMap<WindowContainer, DimState> mDimLayerUsers = new ArrayMap<>();
+
+ /**
+ * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
+ * host, some controller of it, or one of the hosts children.
+ */
+ private WindowContainer mHost;
+
+ Dimmer(WindowContainer host) {
+ mHost = host;
+ }
+
+ SurfaceControl makeDimLayer() {
+ final SurfaceControl control = mHost.makeChildSurface(null)
+ .setParent(mHost.getSurfaceControl())
+ .setColorLayer(true)
+ .setName("Dim Layer for - " + mHost.getName())
+ .build();
+ return control;
+ }
+
+ /**
+ * Retreive the DimState for a given child of the host.
+ */
+ DimState getDimState(WindowContainer container) {
+ DimState state = mDimLayerUsers.get(container);
+ if (state == null) {
+ final SurfaceControl ctl = makeDimLayer();
+ state = new DimState(ctl);
+ /**
+ * See documentation on {@link #dimAbove} to understand lifecycle management of Dim's
+ * via state resetting for Dim's with containers.
+ */
+ if (container == null) {
+ state.mDontReset = true;
+ }
+ mDimLayerUsers.put(container, state);
+ }
+ return state;
+ }
+
+ private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer,
+ float alpha) {
+ final DimState d = getDimState(container);
+ t.show(d.mSurfaceControl);
+ if (container != null) {
+ t.setRelativeLayer(d.mSurfaceControl,
+ container.getSurfaceControl(), relativeLayer);
+ } else {
+ t.setLayer(d.mSurfaceControl, Integer.MAX_VALUE);
+ }
+ t.setAlpha(d.mSurfaceControl, alpha);
+
+ d.mDimming = true;
+ }
+
+ /**
+ * Finish a dim started by dimAbove in the case there was no call to dimAbove.
+ *
+ * @param t A Transaction in which to finish the dim.
+ */
+ void stopDim(SurfaceControl.Transaction t) {
+ DimState d = getDimState(null);
+ t.hide(d.mSurfaceControl);
+ d.mDontReset = false;
+ }
+ /**
+ * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
+ * remove this effect. If the Dim can be assosciated with a particular child of the host
+ * consider using the other variant of dimAbove which ties the Dim lifetime to the child
+ * lifetime more explicitly.
+ *
+ * @param t A transaction in which to apply the Dim.
+ * @param alpha The alpha at which to Dim.
+ */
+ void dimAbove(SurfaceControl.Transaction t, float alpha) {
+ dim(t, null, 1, alpha);
+ }
+
+ /**
+ * Place a dim above the given container, which should be a child of the host container.
+ * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
+ * and the child should call dimAbove again to request the Dim to continue.
+ *
+ * @param t A transaction in which to apply the Dim.
+ * @param container The container which to dim above. Should be a child of our host.
+ * @param alpha The alpha at which to Dim.
+ */
+ void dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
+ dim(t, container, 1, alpha);
+ }
+
+ /**
+ * Like {@link #dimAbove} but places the dim below the given container.
+ *
+ * @param t A transaction in which to apply the Dim.
+ * @param container The container which to dim below. Should be a child of our host.
+ * @param alpha The alpha at which to Dim.
+ */
+
+ void dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
+ dim(t, container, -1, alpha);
+ }
+
+ /**
+ * Mark all dims as pending completion on the next call to {@link #updateDims}
+ *
+ * This is intended for us by the host container, to be called at the beginning of
+ * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
+ * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
+ * a chance to request dims to continue.
+ */
+ void resetDimStates() {
+ for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) {
+ final DimState state = mDimLayerUsers.valueAt(i);
+ if (state.mDontReset == false) {
+ state.mDimming = false;
+ }
+ }
+ }
+
+ /**
+ * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
+ * described in {@link #resetDimStates}.
+ *
+ * @param t A transaction in which to update the dims.
+ * @param bounds The bounds at which to dim.
+ * @return true if any Dims were updated.
+ */
+ boolean updateDims(SurfaceControl.Transaction t, Rect bounds) {
+ boolean didSomething = false;
+ for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) {
+ DimState state = mDimLayerUsers.valueAt(i);
+ // TODO: We want to animate the addition and removal of Dim's instead of immediately
+ // acting. When we do this we need to take care to account for the "Replacing Windows"
+ // case (and seamless dim transfer).
+ if (state.mDimming == false) {
+ mDimLayerUsers.removeAt(i);
+ state.mSurfaceControl.destroy();
+ } else {
+ didSomething = true;
+ // TODO: Once we use geometry from hierarchy this falls away.
+ t.setSize(state.mSurfaceControl, bounds.width(), bounds.height());
+ t.setPosition(state.mSurfaceControl, bounds.left, bounds.top);
+ }
+ }
+ return didSomething;
+ }
+}
diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java
index 4d839d00..41348ba4 100644
--- a/com/android/server/wm/DisplayContent.java
+++ b/com/android/server/wm/DisplayContent.java
@@ -58,10 +58,10 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
@@ -142,14 +142,15 @@ import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.InputDevice;
+import android.view.MagnificationSpec;
import android.view.Surface;
import android.view.SurfaceControl;
-import android.view.WindowManagerPolicy;
+import android.view.SurfaceSession;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.view.IInputMethodClient;
-import android.view.DisplayFrames;
+import com.android.server.policy.WindowManagerPolicy;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -180,8 +181,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
private final TaskStackContainers mTaskStackContainers = new TaskStackContainers();
// Contains all non-app window containers that should be displayed above the app containers
// (e.g. Status bar)
- private final NonAppWindowContainers mAboveAppWindowsContainers =
- new NonAppWindowContainers("mAboveAppWindowsContainers");
+ private final AboveAppWindowContainers mAboveAppWindowsContainers =
+ new AboveAppWindowContainers("mAboveAppWindowsContainers");
// Contains all non-app window containers that should be displayed below the app containers
// (e.g. Wallpaper).
private final NonAppWindowContainers mBelowAppWindowsContainers =
@@ -313,6 +314,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
private final Matrix mTmpMatrix = new Matrix();
private final Region mTmpRegion = new Region();
+ /** Used for handing back size of display */
+ private final Rect mTmpBounds = new Rect();
+
WindowManagerService mService;
/** Remove this display when animation on it has completed. */
@@ -321,8 +325,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
final DockedStackDividerController mDividerControllerLocked;
final PinnedStackController mPinnedStackControllerLocked;
- DimLayerController mDimLayerController;
-
final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
private boolean mHaveBootMsg = false;
@@ -346,10 +348,38 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
// {@code false} if this display is in the processing of being created.
private boolean mDisplayReady = false;
- private final WindowLayersController mLayersController;
WallpaperController mWallpaperController;
int mInputMethodAnimLayerAdjustment;
+ private final SurfaceSession mSession = new SurfaceSession();
+
+ /**
+ * We organize all top-level Surfaces in to the following layers.
+ * mOverlayLayer contains a few Surfaces which are always on top of others
+ * and omitted from Screen-Magnification, for example the strict mode flash or
+ * the magnification overlay itself.
+ * {@link #mWindowingLayer} contains everything else.
+ */
+ private SurfaceControl mOverlayLayer;
+
+ /**
+ * See {@link #mOverlayLayer}
+ */
+ private SurfaceControl mWindowingLayer;
+
+ /**
+ * Specifies the size of the surfaces in {@link #mOverlayLayer} and {@link #mWindowingLayer}.
+ * <p>
+ * For these surfaces currently we use a surface based on the larger of width or height so we
+ * don't have to resize when rotating the display.
+ */
+ private int mSurfaceSize;
+
+ /**
+ * A list of surfaces to be destroyed after {@link #mPendingTransaction} is applied.
+ */
+ private final ArrayList<SurfaceControl> mPendingDestroyingSurfaces = new ArrayList<>();
+
private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
WindowStateAnimator winAnimator = w.mWinAnimator;
if (winAnimator.hasSurface()) {
@@ -503,9 +533,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return true;
};
- private final Consumer<WindowState> mPrepareWindowSurfaces =
- w -> w.mWinAnimator.prepareSurfaceLocked(true);
-
private final Consumer<WindowState> mPerformLayout = w -> {
// Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
// wasting time and funky changes while a window is animating away.
@@ -558,12 +585,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
w.updateLastInsetValues();
}
- // Window frames may have changed. Update dim layer with the new bounds.
- final Task task = w.getTask();
- if (task != null) {
- mDimLayerController.updateDimLayer(task);
- }
-
if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame
+ " mContainingFrame=" + w.mContainingFrame
+ " mDisplayFrame=" + w.mDisplayFrame);
@@ -657,8 +678,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
}
- w.applyDimLayerIfNeeded();
-
if (isDefaultDisplay && obscuredChanged && w.isVisibleLw()
&& mWallpaperController.isWallpaperTarget(w)) {
// This is the wallpaper target and its obscured state changed... make sure the
@@ -741,13 +760,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* initialize direct children.
* @param display May not be null.
* @param service You know.
- * @param layersController window layer controller used to assign layer to the windows on this
- * display.
* @param wallpaperController wallpaper windows controller used to adjust the positioning of the
* wallpaper windows in the window list.
*/
DisplayContent(Display display, WindowManagerService service,
- WindowLayersController layersController, WallpaperController wallpaperController) {
+ WallpaperController wallpaperController) {
if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
+ " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
@@ -756,7 +773,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mDisplay = display;
mDisplayId = display.getDisplayId();
- mLayersController = layersController;
mWallpaperController = wallpaperController;
display.getDisplayInfo(mDisplayInfo);
display.getMetrics(mDisplayMetrics);
@@ -766,7 +782,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
initializeDisplayBaseInfo();
mDividerControllerLocked = new DockedStackDividerController(service, this);
mPinnedStackControllerLocked = new PinnedStackController(service, this);
- mDimLayerController = new DimLayerController(this);
+
+ mSurfaceSize = Math.max(mBaseDisplayHeight, mBaseDisplayWidth);
+
+ final SurfaceControl.Builder b = mService.makeSurfaceBuilder(mSession)
+ .setSize(mSurfaceSize, mSurfaceSize)
+ .setOpaque(true);
+ mWindowingLayer = b.setName("Display Root").build();
+ mOverlayLayer = b.setName("Display Overlays").build();
+
+ getPendingTransaction().setLayer(mWindowingLayer, 0)
+ .setLayerStack(mWindowingLayer, mDisplayId)
+ .show(mWindowingLayer)
+ .setLayer(mOverlayLayer, 1)
+ .setLayerStack(mOverlayLayer, mDisplayId)
+ .show(mOverlayLayer);
+ getPendingTransaction().apply();
// These are the only direct children we should ever have and they are permanent.
super.addChild(mBelowAppWindowsContainers, null);
@@ -1030,11 +1061,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
setLayoutNeeded();
final int[] anim = new int[2];
- if (isDimming()) {
- anim[0] = anim[1] = 0;
- } else {
- mService.mPolicy.selectRotationAnimationLw(anim);
- }
+ mService.mPolicy.selectRotationAnimationLw(anim);
if (!rotateSeamlessly) {
mService.startFreezingDisplayLocked(inTransaction, anim[0], anim[1], this);
@@ -1071,8 +1098,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
// it doesn't support hardware OpenGL emulation yet.
if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
&& screenRotationAnimation.hasScreenshot()) {
- if (screenRotationAnimation.setRotationInTransaction(
- rotation, mService.mFxSession,
+ if (screenRotationAnimation.setRotationInTransaction(rotation,
MAX_ANIMATION_DURATION, mService.getTransitionAnimationScaleLocked(),
mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight)) {
mService.scheduleAnimationLocked();
@@ -1201,6 +1227,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,
mCompatDisplayMetrics);
}
+
+ updateBounds();
return mDisplayInfo;
}
@@ -1519,8 +1547,17 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
// See {@link PhoneWindowManager#setInitialDisplaySize}...sigh...
mService.reconfigureDisplayLocked(this);
- getDockedDividerController().onConfigurationChanged();
- getPinnedStackController().onConfigurationChanged();
+ final DockedStackDividerController dividerController = getDockedDividerController();
+
+ if (dividerController != null) {
+ getDockedDividerController().onConfigurationChanged();
+ }
+
+ final PinnedStackController pinnedStackController = getPinnedStackController();
+
+ if (pinnedStackController != null) {
+ getPinnedStackController().onConfigurationChanged();
+ }
}
/**
@@ -1659,33 +1696,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
}
- void getLogicalDisplayRect(Rect out) {
- // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked.
- final int orientation = mDisplayInfo.rotation;
- boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
- final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
- final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
- int width = mDisplayInfo.logicalWidth;
- int left = (physWidth - width) / 2;
- int height = mDisplayInfo.logicalHeight;
- int top = (physHeight - height) / 2;
- out.set(left, top, left + width, top + height);
- }
-
- private void getLogicalDisplayRect(Rect out, int orientation) {
- getLogicalDisplayRect(out);
-
- // Rotate the Rect if needed.
- final int currentRotation = mDisplayInfo.rotation;
- final int rotationDelta = deltaRotation(currentRotation, orientation);
- if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
- createRotationMatrix(rotationDelta, mBaseDisplayWidth, mBaseDisplayHeight, mTmpMatrix);
- mTmpRectF.set(out);
- mTmpMatrix.mapRect(mTmpRectF);
- mTmpRectF.round(out);
- }
- }
-
/**
* If display metrics changed, overrides are not set and it's not just a rotation - update base
* values.
@@ -1753,6 +1763,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+
+ updateBounds();
}
void getContentRect(Rect out) {
@@ -1907,22 +1919,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
}
- boolean animateDimLayers() {
- return mDimLayerController.animateDimLayers();
- }
-
- private void resetDimming() {
- mDimLayerController.resetDimming();
- }
-
- boolean isDimming() {
- return mDimLayerController.isDimming();
- }
-
- private void stopDimmingIfNeeded() {
- mDimLayerController.stopDimmingIfNeeded();
- }
-
@Override
void removeIfPossible() {
if (isAnimating()) {
@@ -1938,7 +1934,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
try {
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
- mDimLayerController.close();
if (mService.canDispatchPointerEvents()) {
if (mTapDetector != null) {
mService.unregisterPointerEventListener(mTapDetector);
@@ -1947,6 +1942,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mService.unregisterPointerEventListener(mService.mMousePositionTracker);
}
}
+ // The pending transaction won't be applied so we should
+ // just clean up any surfaces pending destruction.
+ onPendingTransactionApplied();
} finally {
mRemovingDisplay = false;
}
@@ -2096,7 +2094,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
void rotateBounds(int oldRotation, int newRotation, Rect bounds) {
- getLogicalDisplayRect(mTmpRect, newRotation);
+ getBounds(mTmpRect, newRotation);
// Compute a transform matrix to undo the coordinate space transformation,
// and present the window at the same physical position it previously occupied.
@@ -2228,8 +2226,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
token.dump(pw, " ");
}
}
- pw.println();
- mDimLayerController.dump(prefix, pw);
+
pw.println();
// Dump stack references
@@ -2342,10 +2339,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
/** Updates the layer assignment of windows on this display. */
void assignWindowLayers(boolean setLayoutNeeded) {
- mLayersController.assignWindowLayers(this);
+ assignChildLayers(getPendingTransaction());
if (setLayoutNeeded) {
setLayoutNeeded();
}
+
+ // We accumlate the layer changes in-to "getPendingTransaction()" but we defer
+ // the application of this transaction until the animation pass triggers
+ // prepareSurfaces. This allows us to synchronize Z-ordering changes with
+ // the hiding and showing of surfaces.
+ scheduleAnimation();
}
// TODO: This should probably be called any time a visual change is made to the hierarchy like
@@ -2701,10 +2704,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
}
- void prepareWindowSurfaces() {
- forAllWindows(mPrepareWindowSurfaces, false /* traverseTopToBottom */);
- }
-
boolean inputMethodClientHasFocus(IInputMethodClient client) {
final WindowState imFocus = computeImeTarget(false /* updateImeTarget */);
if (imFocus == null) {
@@ -2846,7 +2845,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
} while (pendingLayoutChanges != 0);
mTmpApplySurfaceChangesTransactionState.reset();
- resetDimming();
mTmpRecoveringMemory = recoveringMemory;
forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
@@ -2857,8 +2855,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mTmpApplySurfaceChangesTransactionState.preferredModeId,
true /* inTraversal, must call performTraversalInTrans... below */);
- stopDimmingIfNeeded();
-
final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
if (wallpaperVisible != mLastWallpaperVisible) {
mLastWallpaperVisible = wallpaperVisible;
@@ -2875,6 +2871,44 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return mTmpApplySurfaceChangesTransactionState.focusDisplayed;
}
+ private void updateBounds() {
+ calculateBounds(mTmpBounds);
+ setBounds(mTmpBounds);
+ }
+
+ // Determines the current display bounds based on the current state
+ private void calculateBounds(Rect out) {
+ // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked.
+ final int orientation = mDisplayInfo.rotation;
+ boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
+ final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
+ final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
+ int width = mDisplayInfo.logicalWidth;
+ int left = (physWidth - width) / 2;
+ int height = mDisplayInfo.logicalHeight;
+ int top = (physHeight - height) / 2;
+ out.set(left, top, left + width, top + height);
+ }
+
+ @Override
+ public void getBounds(Rect out) {
+ calculateBounds(out);
+ }
+
+ private void getBounds(Rect out, int orientation) {
+ getBounds(out);
+
+ // Rotate the Rect if needed.
+ final int currentRotation = mDisplayInfo.rotation;
+ final int rotationDelta = deltaRotation(currentRotation, orientation);
+ if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
+ createRotationMatrix(rotationDelta, mBaseDisplayWidth, mBaseDisplayHeight, mTmpMatrix);
+ mTmpRectF.set(out);
+ mTmpMatrix.mapRect(mTmpRectF);
+ mTmpRectF.round(out);
+ }
+ }
+
void performLayout(boolean initial, boolean updateInputWindows) {
if (!isLayoutNeeded()) {
return;
@@ -2935,241 +2969,30 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* Takes a snapshot of the display. In landscape mode this grabs the whole screen.
* In portrait mode, it grabs the full screenshot.
*
- * @param width the width of the target bitmap
- * @param height the height of the target bitmap
- * @param includeFullDisplay true if the screen should not be cropped before capture
- * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
* @param config of the output bitmap
* @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
- * @param includeDecor whether to include window decors, like the status or navigation bar
- * background of the window
*/
- Bitmap screenshotApplications(IBinder appToken, int width, int height,
- boolean includeFullDisplay, float frameScale, Bitmap.Config config,
- boolean wallpaperOnly, boolean includeDecor) {
- Bitmap bitmap = screenshotApplications(appToken, width, height, includeFullDisplay,
- frameScale, wallpaperOnly, includeDecor, SurfaceControl::screenshot);
- if (bitmap == null) {
- return null;
- }
-
- if (DEBUG_SCREENSHOT) {
- // TEST IF IT's ALL BLACK
- int[] buffer = new int[bitmap.getWidth() * bitmap.getHeight()];
- bitmap.getPixels(buffer, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(),
- bitmap.getHeight());
- boolean allBlack = true;
- final int firstColor = buffer[0];
- for (int i = 0; i < buffer.length; i++) {
- if (buffer[i] != firstColor) {
- allBlack = false;
- break;
- }
- }
- if (allBlack) {
- final WindowState appWin = mScreenshotApplicationState.appWin;
- final int maxLayer = mScreenshotApplicationState.maxLayer;
- final int minLayer = mScreenshotApplicationState.minLayer;
- Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" +
- Integer.toHexString(firstColor) + ")! mSurfaceLayer=" +
- (appWin != null ?
- appWin.mWinAnimator.mSurfaceController.getLayer() : "null") +
- " minLayer=" + minLayer + " maxLayer=" + maxLayer);
- }
- }
-
- // Create a copy of the screenshot that is immutable and backed in ashmem.
- // This greatly reduces the overhead of passing the bitmap between processes.
- Bitmap ret = bitmap.createAshmemBitmap(config);
- bitmap.recycle();
- return ret;
- }
-
- GraphicBuffer screenshotApplicationsToBuffer(IBinder appToken, int width, int height,
- boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
- boolean includeDecor) {
- return screenshotApplications(appToken, width, height, includeFullDisplay, frameScale,
- wallpaperOnly, includeDecor, SurfaceControl::screenshotToBuffer);
- }
-
- private <E> E screenshotApplications(IBinder appToken, int width, int height,
- boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
- boolean includeDecor, Screenshoter<E> screenshoter) {
- int dw = mDisplayInfo.logicalWidth;
- int dh = mDisplayInfo.logicalHeight;
- if (dw == 0 || dh == 0) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
- + ": returning null. logical widthxheight=" + dw + "x" + dh);
- return null;
- }
-
- E bitmap;
-
- mScreenshotApplicationState.reset(appToken == null && !wallpaperOnly);
- final Rect frame = new Rect();
- final Rect stackBounds = new Rect();
-
- final int aboveAppLayer = (mService.mPolicy.getWindowLayerFromTypeLw(TYPE_APPLICATION) + 1)
- * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
- final MutableBoolean mutableIncludeFullDisplay = new MutableBoolean(includeFullDisplay);
- synchronized(mService.mWindowMap) {
+ Bitmap screenshotDisplay(Bitmap.Config config, boolean wallpaperOnly) {
+ synchronized (mService.mWindowMap) {
if (!mService.mPolicy.isScreenOn()) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Attempted to take screenshot while display"
- + " was off.");
- return null;
- }
- // Figure out the part of the screen that is actually the app.
- mScreenshotApplicationState.appWin = null;
- forAllWindows(w -> {
- if (!w.mHasSurface) {
- return false;
- }
- if (w.mLayer >= aboveAppLayer) {
- return false;
- }
- if (wallpaperOnly && !w.mIsWallpaper) {
- return false;
- }
- if (w.mIsImWindow) {
- return false;
- } else if (w.mIsWallpaper) {
- // If this is the wallpaper layer and we're only looking for the wallpaper layer
- // then the target window state is this one.
- if (wallpaperOnly) {
- mScreenshotApplicationState.appWin = w;
- }
-
- if (mScreenshotApplicationState.appWin == null) {
- // We have not ran across the target window yet, so it is probably behind
- // the wallpaper. This can happen when the keyguard is up and all windows
- // are moved behind the wallpaper. We don't want to include the wallpaper
- // layer in the screenshot as it will cover-up the layer of the target
- // window.
- return false;
- }
- // Fall through. The target window is in front of the wallpaper. For this
- // case we want to include the wallpaper layer in the screenshot because
- // the target window might have some transparent areas.
- } else if (appToken != null) {
- if (w.mAppToken == null || w.mAppToken.token != appToken) {
- // This app window is of no interest if it is not associated with the
- // screenshot app.
- return false;
- }
- mScreenshotApplicationState.appWin = w;
- }
-
- // Include this window.
-
- final WindowStateAnimator winAnim = w.mWinAnimator;
- int layer = winAnim.mSurfaceController.getLayer();
- if (mScreenshotApplicationState.maxLayer < layer) {
- mScreenshotApplicationState.maxLayer = layer;
- }
- if (mScreenshotApplicationState.minLayer > layer) {
- mScreenshotApplicationState.minLayer = layer;
+ if (DEBUG_SCREENSHOT) {
+ Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
}
-
- // Don't include wallpaper in bounds calculation
- if (!w.mIsWallpaper && !mutableIncludeFullDisplay.value) {
- if (includeDecor) {
- final Task task = w.getTask();
- if (task != null) {
- task.getBounds(frame);
- } else {
-
- // No task bounds? Too bad! Ain't no screenshot then.
- return true;
- }
- } else {
- final Rect wf = w.mFrame;
- final Rect cr = w.mContentInsets;
- int left = wf.left + cr.left;
- int top = wf.top + cr.top;
- int right = wf.right - cr.right;
- int bottom = wf.bottom - cr.bottom;
- frame.union(left, top, right, bottom);
- w.getVisibleBounds(stackBounds);
- if (!Rect.intersects(frame, stackBounds)) {
- // Set frame empty if there's no intersection.
- frame.setEmpty();
- }
- }
- }
-
- final boolean foundTargetWs =
- (w.mAppToken != null && w.mAppToken.token == appToken)
- || (mScreenshotApplicationState.appWin != null && wallpaperOnly);
- if (foundTargetWs && winAnim.getShown() && winAnim.mLastAlpha > 0f) {
- mScreenshotApplicationState.screenshotReady = true;
- }
-
- if (w.isObscuringDisplay()){
- return true;
- }
- return false;
- }, true /* traverseTopToBottom */);
-
- final WindowState appWin = mScreenshotApplicationState.appWin;
- final boolean screenshotReady = mScreenshotApplicationState.screenshotReady;
- final int maxLayer = mScreenshotApplicationState.maxLayer;
- final int minLayer = mScreenshotApplicationState.minLayer;
-
- if (appToken != null && appWin == null) {
- // Can't find a window to snapshot.
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM,
- "Screenshot: Couldn't find a surface matching " + appToken);
return null;
}
- if (!screenshotReady) {
- Slog.i(TAG_WM, "Failed to capture screenshot of " + appToken +
- " appWin=" + (appWin == null ? "null" : (appWin + " drawState=" +
- appWin.mWinAnimator.mDrawState)));
+ if (wallpaperOnly && !shouldScreenshotWallpaper()) {
return null;
}
- // Screenshot is ready to be taken. Everything from here below will continue
- // through the bottom of the loop and return a value. We only stay in the loop
- // because we don't want to release the mWindowMap lock until the screenshot is
- // taken.
-
- if (maxLayer == 0) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
- + ": returning null maxLayer=" + maxLayer);
- return null;
- }
+ int dw = mDisplayInfo.logicalWidth;
+ int dh = mDisplayInfo.logicalHeight;
- if (!mutableIncludeFullDisplay.value) {
- // Constrain frame to the screen size.
- if (!frame.intersect(0, 0, dw, dh)) {
- frame.setEmpty();
- }
- } else {
- // Caller just wants entire display.
- frame.set(0, 0, dw, dh);
- }
- if (frame.isEmpty()) {
+ if (dw <= 0 || dh <= 0) {
return null;
}
- if (width < 0) {
- width = (int) (frame.width() * frameScale);
- }
- if (height < 0) {
- height = (int) (frame.height() * frameScale);
- }
-
- // Tell surface flinger what part of the image to crop. Take the top
- // right part of the application, and crop the larger dimension to fit.
- Rect crop = new Rect(frame);
- if (width / (float) frame.width() < height / (float) frame.height()) {
- int cropWidth = (int)((float)width / (float)height * frame.height());
- crop.right = crop.left + cropWidth;
- } else {
- int cropHeight = (int)((float)height / (float)width * frame.width());
- crop.bottom = crop.top + cropHeight;
- }
+ final Rect frame = new Rect(0, 0, dw, dh);
// The screenshot API does not apply the current screen rotation.
int rot = mDisplay.getRotation();
@@ -3178,43 +3001,52 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
rot = (rot == ROTATION_90) ? ROTATION_270 : ROTATION_90;
}
- // Surfaceflinger is not aware of orientation, so convert our logical
- // crop to surfaceflinger's portrait orientation.
- convertCropForSurfaceFlinger(crop, rot, dw, dh);
-
- if (DEBUG_SCREENSHOT) {
- Slog.i(TAG_WM, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to "
- + maxLayer + " appToken=" + appToken);
- forAllWindows(w -> {
- final WindowSurfaceController controller = w.mWinAnimator.mSurfaceController;
- Slog.i(TAG_WM, w + ": " + w.mLayer
- + " animLayer=" + w.mWinAnimator.mAnimLayer
- + " surfaceLayer=" + ((controller == null)
- ? "null" : controller.getLayer()));
- }, false /* traverseTopToBottom */);
- }
+ // SurfaceFlinger is not aware of orientation, so convert our logical
+ // crop to SurfaceFlinger's portrait orientation.
+ convertCropForSurfaceFlinger(frame, rot, dw, dh);
final ScreenRotationAnimation screenRotationAnimation =
mService.mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY);
final boolean inRotation = screenRotationAnimation != null &&
screenRotationAnimation.isAnimating();
- if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM,
- "Taking screenshot while rotating");
-
- // We force pending transactions to flush before taking
- // the screenshot by pushing an empty synchronous transaction.
- SurfaceControl.openTransaction();
- SurfaceControl.closeTransactionSync();
+ if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating");
- bitmap = screenshoter.screenshot(crop, width, height, minLayer, maxLayer,
- inRotation, rot);
+ // TODO(b/68392460): We should screenshot Task controls directly
+ // but it's difficult at the moment as the Task doesn't have the
+ // correct size set.
+ final Bitmap bitmap = SurfaceControl.screenshot(frame, dw, dh, 0, 1, inRotation, rot);
if (bitmap == null) {
- Slog.w(TAG_WM, "Screenshot failure taking screenshot for (" + dw + "x" + dh
- + ") to layer " + maxLayer);
+ Slog.w(TAG_WM, "Failed to take screenshot");
return null;
}
+
+ // Create a copy of the screenshot that is immutable and backed in ashmem.
+ // This greatly reduces the overhead of passing the bitmap between processes.
+ final Bitmap ret = bitmap.createAshmemBitmap(config);
+ bitmap.recycle();
+ return ret;
}
- return bitmap;
+ }
+
+ private boolean shouldScreenshotWallpaper() {
+ MutableBoolean screenshotReady = new MutableBoolean(false);
+
+ forAllWindows(w -> {
+ if (!w.mIsWallpaper) {
+ return false;
+ }
+
+ // Found the wallpaper window
+ final WindowStateAnimator winAnim = w.mWinAnimator;
+
+ if (winAnim.getShown() && winAnim.mLastAlpha > 0f) {
+ screenshotReady.value = true;
+ }
+
+ return true;
+ }, true /* traverseTopToBottom */);
+
+ return screenshotReady.value;
}
// TODO: Can this use createRotationMatrix()?
@@ -3366,6 +3198,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* I.e Activities.
*/
private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
+ /**
+ * A control placed at the appropriate level for transitions to occur.
+ */
+ SurfaceControl mAnimationLayer = null;
// 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.
@@ -3677,13 +3513,85 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
// to prevent freezing/unfreezing the display too early.
return mLastOrientation;
}
+
+ @Override
+ void assignChildLayers(SurfaceControl.Transaction t) {
+ final int NORMAL_STACK_STATE = 0;
+ final int BOOSTED_STATE = 1;
+ final int ALWAYS_ON_TOP_STATE = 2;
+
+ // We allow stacks to change visual order from the AM specified order due to
+ // Z-boosting during animations. However we must take care to ensure TaskStacks
+ // which are marked as alwaysOnTop remain that way.
+ int layer = 0;
+ for (int state = 0; state <= ALWAYS_ON_TOP_STATE; state++) {
+ for (int i = 0; i < mChildren.size(); i++) {
+ final TaskStack s = mChildren.get(i);
+ layer++;
+ if (state == NORMAL_STACK_STATE) {
+ s.assignLayer(t, layer);
+ } else if (state == BOOSTED_STATE && s.needsZBoost()) {
+ s.assignLayer(t, layer);
+ } else if (state == ALWAYS_ON_TOP_STATE &&
+ s.isAlwaysOnTop()) {
+ s.assignLayer(t, layer);
+ }
+ s.assignChildLayers(t);
+ }
+ // The appropriate place for App-Transitions to occur is right
+ // above all other animations but still below things in the Picture-and-Picture
+ // windowing mode.
+ if (state == BOOSTED_STATE && mAnimationLayer != null) {
+ t.setLayer(mAnimationLayer, layer + 1);
+ }
+ }
+ }
+
+ @Override
+ void onParentSet() {
+ super.onParentSet();
+ if (getParent() != null) {
+ mAnimationLayer = makeSurface().build();
+ } else {
+ mAnimationLayer.destroy();
+ mAnimationLayer = null;
+ }
+ }
+ }
+
+ private final class AboveAppWindowContainers extends NonAppWindowContainers {
+ AboveAppWindowContainers(String name) {
+ super(name);
+ }
+
+ void assignChildLayers(SurfaceControl.Transaction t, WindowContainer imeContainer) {
+ boolean needAssignIme = imeContainer != null
+ && imeContainer.getSurfaceControl() != null;
+ for (int j = 0; j < mChildren.size(); ++j) {
+ final WindowToken wt = mChildren.get(j);
+ wt.assignLayer(t, j);
+ wt.assignChildLayers(t);
+
+ int layer = mService.mPolicy.getWindowLayerFromTypeLw(
+ wt.windowType, wt.mOwnerCanManageAppTokens);
+ if (needAssignIme && layer >= TYPE_INPUT_METHOD_DIALOG) {
+ t.setRelativeLayer(imeContainer.getSurfaceControl(),
+ wt.getSurfaceControl(), -1);
+ needAssignIme = false;
+ }
+ }
+ if (needAssignIme) {
+ t.setRelativeLayer(imeContainer.getSurfaceControl(),
+ getSurfaceControl(), Integer.MIN_VALUE);
+ }
+ }
}
/**
* Window container class that contains all containers on this display that are not related to
* Apps. E.g. status bar.
*/
- private final class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
+ private class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
/**
* Compares two child window tokens returns -1 if the first is lesser than the second in
* terms of z-order and 1 otherwise.
@@ -3752,12 +3660,124 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
}
+ SurfaceControl.Builder makeSurface(SurfaceSession s) {
+ return mService.makeSurfaceBuilder(s)
+ .setParent(mWindowingLayer);
+ }
+
+ @Override
+ SurfaceSession getSession() {
+ return mSession;
+ }
+
+ @Override
+ SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+ SurfaceSession s = child != null ? child.getSession() : getSession();
+ final SurfaceControl.Builder b = mService.makeSurfaceBuilder(s);
+ b.setSize(mSurfaceSize, mSurfaceSize);
+
+ if (child == null) {
+ return b;
+ }
+
+ return b.setName(child.getName())
+ .setParent(mWindowingLayer);
+ }
+
+ /**
+ * The makeSurface variants are for use by the window-container
+ * hierarchy. makeOverlay here is a function for various non windowing
+ * overlays like the ScreenRotation screenshot, the Strict Mode Flash
+ * and other potpourii.
+ */
+ SurfaceControl.Builder makeOverlay() {
+ return mService.makeSurfaceBuilder(mSession)
+ .setParent(mOverlayLayer);
+ }
+
+ void applyMagnificationSpec(MagnificationSpec spec) {
+ applyMagnificationSpec(getPendingTransaction(), spec);
+ getPendingTransaction().apply();
+ }
+
+ @Override
+ void onParentSet() {
+ // Since we are the top of the SurfaceControl hierarchy here
+ // we create the root surfaces explicitly rather than chaining
+ // up as the default implementation in onParentSet does. So we
+ // explicitly do NOT call super here.
+ }
+
+ @Override
+ void assignChildLayers(SurfaceControl.Transaction t) {
+ t.setLayer(mOverlayLayer, 1)
+ .setLayer(mWindowingLayer, 0);
+
+ // These are layers as children of "mWindowingLayer"
+ mBelowAppWindowsContainers.assignLayer(t, 0);
+ mTaskStackContainers.assignLayer(t, 1);
+ mAboveAppWindowsContainers.assignLayer(t, 2);
+
+ WindowState imeTarget = mService.mInputMethodTarget;
+ boolean needAssignIme = true;
+
+ // In the case where we have an IME target that is not in split-screen
+ // mode IME assignment is easy. We just need the IME to go directly above
+ // the target. This way children of the target will naturally go above the IME
+ // and everyone is happy.
+ //
+ // In the case of split-screen windowing mode, we need to elevate the IME above the
+ // docked divider while keeping the app itself below the docked divider, so instead
+ // we use relative layering of the IME targets child windows, and place the
+ // IME in the non-app layer (see {@link AboveAppWindowContainers#assignChildLayers}).
+ //
+ // In the case where we have no IME target we assign it where it's base layer would
+ // place it in the AboveAppWindowContainers.
+ if (imeTarget != null && !imeTarget.inSplitScreenWindowingMode()
+ && (imeTarget.getSurfaceControl() != null)) {
+ t.setRelativeLayer(mImeWindowsContainers.getSurfaceControl(),
+ imeTarget.getSurfaceControl(),
+ // TODO: We need to use an extra level on the app surface to ensure
+ // this is always above SurfaceView but always below attached window.
+ 1);
+ needAssignIme = false;
+ }
+
+ // Above we have assigned layers to our children, now we ask them to assign
+ // layers to their children.
+ mBelowAppWindowsContainers.assignChildLayers(t);
+ mTaskStackContainers.assignChildLayers(t);
+ mAboveAppWindowsContainers.assignChildLayers(t,
+ needAssignIme == true ? mImeWindowsContainers : null);
+ mImeWindowsContainers.assignChildLayers(t);
+ }
+
+ /**
+ * Here we satisfy an unfortunate special case of the IME in split-screen mode. Imagine
+ * that the IME target is one of the docked applications. We'd like the docked divider to be
+ * above both of the applications, and we'd like the IME to be above the docked divider.
+ * However we need child windows of the applications to be above the IME (Text drag handles).
+ * This is a non-strictly hierarcical layering and we need to break out of the Z ordering
+ * somehow. We do this by relatively ordering children of the target to the IME in cooperation
+ * with {@link #WindowState#assignLayer}
+ */
+ void assignRelativeLayerForImeTargetChild(SurfaceControl.Transaction t, WindowContainer child) {
+ t.setRelativeLayer(child.getSurfaceControl(), mImeWindowsContainers.getSurfaceControl(), 1);
+ }
+
+ @Override
+ void destroyAfterPendingTransaction(SurfaceControl surface) {
+ mPendingDestroyingSurfaces.add(surface);
+ }
+
/**
- * Interface to screenshot into various types, i.e. {@link Bitmap} and {@link GraphicBuffer}.
+ * Destroys any surfaces that have been put into the pending list with
+ * {@link #destroyAfterTransaction}.
*/
- @FunctionalInterface
- private interface Screenshoter<E> {
- E screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
- boolean useIdentityTransform, int rotation);
+ void onPendingTransactionApplied() {
+ for (int i = mPendingDestroyingSurfaces.size() - 1; i >= 0; i--) {
+ mPendingDestroyingSurfaces.get(i).destroy();
+ }
+ mPendingDestroyingSurfaces.clear();
}
}
diff --git a/android/view/DisplayFrames.java b/com/android/server/wm/DisplayFrames.java
index e6861d83..0249713e 100644
--- a/android/view/DisplayFrames.java
+++ b/com/android/server/wm/DisplayFrames.java
@@ -11,10 +11,10 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package android.view;
+package com.android.server.wm;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
@@ -23,6 +23,7 @@ import static com.android.server.wm.proto.DisplayFramesProto.STABLE_BOUNDS;
import android.graphics.Rect;
import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
import java.io.PrintWriter;
diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java
index d79ba897..a37598e7 100644
--- a/com/android/server/wm/DockedStackDividerController.java
+++ b/com/android/server/wm/DockedStackDividerController.java
@@ -54,7 +54,6 @@ import android.view.inputmethod.InputMethodManagerInternal;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DockedDividerUtils;
import com.android.server.LocalServices;
-import com.android.server.wm.DimLayer.DimLayerUser;
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
@@ -62,7 +61,7 @@ import java.io.PrintWriter;
/**
* Keeps information about the docked stack divider.
*/
-public class DockedStackDividerController implements DimLayerUser {
+public class DockedStackDividerController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM;
@@ -114,7 +113,6 @@ public class DockedStackDividerController implements DimLayerUser {
private boolean mLastVisibility = false;
private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners
= new RemoteCallbackList<>();
- private final DimLayer mDimLayer;
private boolean mMinimizedDock;
private int mOriginalDockedSide = DOCKED_INVALID;
@@ -141,13 +139,12 @@ public class DockedStackDividerController implements DimLayerUser {
private boolean mImeHideRequested;
private final Rect mLastDimLayerRect = new Rect();
private float mLastDimLayerAlpha;
+ private TaskStack mDimmedStack;
DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
mDisplayContent = displayContent;
final Context context = service.mContext;
- mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId(),
- "DockedStackDim");
mMinimizedDockInterpolator = AnimationUtils.loadInterpolator(
context, android.R.interpolator.fast_out_slow_in);
loadDimens();
@@ -463,6 +460,11 @@ public class DockedStackDividerController implements DimLayerUser {
}
mOriginalDockedSide = DOCKED_INVALID;
setMinimizedDockedStack(false /* minimizedDock */, false /* animate */);
+
+ if (mDimmedStack != null) {
+ mDimmedStack.stopDimming();
+ mDimmedStack = null;
+ }
}
/**
@@ -564,34 +566,12 @@ public class DockedStackDividerController implements DimLayerUser {
final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack();
boolean visibleAndValid = visible && stack != null && dockedStack != null;
if (visibleAndValid) {
- stack.getDimBounds(mTmpRect);
- if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
- if (!mLastDimLayerRect.equals(mTmpRect) || mLastDimLayerAlpha != alpha) {
- try {
- // TODO: This should use the regular animation transaction - here and below
- mService.openSurfaceTransaction();
- mDimLayer.setBounds(mTmpRect);
- mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
- } finally {
- mService.closeSurfaceTransaction("setResizeDimLayer");
- }
- }
- mLastDimLayerRect.set(mTmpRect);
- mLastDimLayerAlpha = alpha;
- } else {
- visibleAndValid = false;
- }
+ mDimmedStack = stack;
+ stack.dim(alpha);
}
- if (!visibleAndValid) {
- if (mLastDimLayerAlpha != 0f) {
- try {
- mService.openSurfaceTransaction();
- mDimLayer.hide();
- } finally {
- mService.closeSurfaceTransaction("setResizeDimLayer");
- }
- }
- mLastDimLayerAlpha = 0f;
+ if (!visibleAndValid && stack != null) {
+ mDimmedStack = null;
+ stack.stopDimming();
}
}
@@ -675,8 +655,8 @@ public class DockedStackDividerController implements DimLayerUser {
}
private boolean isWithinDisplay(Task task) {
- task.mStack.getBounds(mTmpRect);
- mDisplayContent.getLogicalDisplayRect(mTmpRect2);
+ task.getBounds(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect2);
return mTmpRect.intersect(mTmpRect2);
}
@@ -829,12 +809,8 @@ public class DockedStackDividerController implements DimLayerUser {
return animateForMinimizedDockedStack(now);
} else if (mAnimatingForIme) {
return animateForIme(now);
- } else {
- if (mDimLayer != null && mDimLayer.isDimming()) {
- mDimLayer.setLayer(getResizeDimLayer());
- }
- return false;
}
+ return false;
}
private boolean animateForIme(long now) {
@@ -942,27 +918,6 @@ public class DockedStackDividerController implements DimLayerUser {
+ (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST);
}
- @Override
- public boolean dimFullscreen() {
- return false;
- }
-
- @Override
- public DisplayInfo getDisplayInfo() {
- return mDisplayContent.getDisplayInfo();
- }
-
- @Override
- public boolean isAttachedToDisplay() {
- return mDisplayContent != null;
- }
-
- @Override
- public void getDimBounds(Rect outBounds) {
- // This dim layer user doesn't need this.
- }
-
- @Override
public String toShortString() {
return TAG;
}
@@ -977,10 +932,6 @@ public class DockedStackDividerController implements DimLayerUser {
pw.println(prefix + " mMinimizedDock=" + mMinimizedDock);
pw.println(prefix + " mAdjustedForIme=" + mAdjustedForIme);
pw.println(prefix + " mAdjustedForDivider=" + mAdjustedForDivider);
- if (mDimLayer.isDimming()) {
- pw.println(prefix + " Dim layer is dimming: ");
- mDimLayer.printTo(prefix + " ", pw);
- }
}
void writeToProto(ProtoOutputStream proto, long fieldId) {
diff --git a/com/android/server/wm/DragDropController.java b/com/android/server/wm/DragDropController.java
index 4567e101..65951dc6 100644
--- a/com/android/server/wm/DragDropController.java
+++ b/com/android/server/wm/DragDropController.java
@@ -36,9 +36,10 @@ import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
-import android.view.WindowManagerInternal.IDragDropCallback;
+
import com.android.internal.util.Preconditions;
import com.android.server.input.InputWindowHandle;
+import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
/**
* Managing drag and drop operations initiated by View#startDragAndDrop.
diff --git a/com/android/server/wm/DragState.java b/com/android/server/wm/DragState.java
index e81d366b..112e62f2 100644
--- a/com/android/server/wm/DragState.java
+++ b/com/android/server/wm/DragState.java
@@ -645,15 +645,15 @@ class DragState {
try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
transaction.setPosition(
mSurfaceControl,
- (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X),
- (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y));
+ (float) animation.getAnimatedValue(ANIMATED_PROPERTY_X),
+ (float) animation.getAnimatedValue(ANIMATED_PROPERTY_Y));
transaction.setAlpha(
mSurfaceControl,
- (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
+ (float) animation.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
transaction.setMatrix(
mSurfaceControl,
- (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
- 0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
+ (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
+ 0, (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
transaction.apply();
}
}
diff --git a/com/android/server/wm/EmulatorDisplayOverlay.java b/com/android/server/wm/EmulatorDisplayOverlay.java
index 8bec8d75..fddf6ca2 100644
--- a/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -49,19 +49,19 @@ class EmulatorDisplayOverlay {
private int mRotation;
private boolean mVisible;
- public EmulatorDisplayOverlay(Context context, Display display, SurfaceSession session,
+ public EmulatorDisplayOverlay(Context context, DisplayContent dc,
int zOrder) {
+ final Display display = dc.getDisplay();
mScreenSize = new Point();
display.getSize(mScreenSize);
SurfaceControl ctrl = null;
try {
- ctrl = new SurfaceControl.Builder(session)
+ ctrl = dc.makeOverlay()
.setName("EmulatorDisplayOverlay")
.setSize(mScreenSize.x, mScreenSize.y)
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- ctrl.setLayerStack(display.getLayerStack());
ctrl.setLayer(zOrder);
ctrl.setPosition(0, 0);
ctrl.show();
diff --git a/com/android/server/wm/InputMonitor.java b/com/android/server/wm/InputMonitor.java
index a766097c..7e29a3aa 100644
--- a/com/android/server/wm/InputMonitor.java
+++ b/com/android/server/wm/InputMonitor.java
@@ -47,11 +47,11 @@ import android.view.InputChannel;
import android.view.InputEventReceiver;
import android.view.KeyEvent;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
import com.android.server.input.InputApplicationHandle;
import com.android.server.input.InputManagerService;
import com.android.server.input.InputWindowHandle;
+import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -661,8 +661,8 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
if (w.inPinnedWindowingMode()) {
if (mAddPipInputConsumerHandle
&& (inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer)) {
- // Update the bounds of the Pip input consumer to match the Pinned stack
- w.getStack().getBounds(pipTouchableBounds);
+ // Update the bounds of the Pip input consumer to match the window bounds.
+ w.getBounds(pipTouchableBounds);
pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds);
addInputWindowHandle(pipInputConsumer.mWindowHandle);
mAddPipInputConsumerHandle = false;
diff --git a/com/android/server/wm/KeyguardDisableHandler.java b/com/android/server/wm/KeyguardDisableHandler.java
index 2eb186b5..4a20f1a0 100644
--- a/com/android/server/wm/KeyguardDisableHandler.java
+++ b/com/android/server/wm/KeyguardDisableHandler.java
@@ -29,7 +29,8 @@ import android.os.RemoteException;
import android.os.TokenWatcher;
import android.util.Log;
import android.util.Pair;
-import android.view.WindowManagerPolicy;
+
+import com.android.server.policy.WindowManagerPolicy;
public class KeyguardDisableHandler extends Handler {
private static final String TAG = TAG_WITH_CLASS_NAME ? "KeyguardDisableHandler" : TAG_WM;
diff --git a/com/android/server/wm/PinnedStackWindowController.java b/com/android/server/wm/PinnedStackWindowController.java
index 41f076d7..b021a722 100644
--- a/com/android/server/wm/PinnedStackWindowController.java
+++ b/com/android/server/wm/PinnedStackWindowController.java
@@ -106,7 +106,7 @@ public class PinnedStackWindowController extends StackWindowController {
} else {
// Otherwise, use the display bounds
toBounds = new Rect();
- mContainer.getDisplayContent().getLogicalDisplayRect(toBounds);
+ mContainer.getDisplayContent().getBounds(toBounds);
}
} else if (fromFullscreen) {
schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
diff --git a/com/android/server/wm/PointerEventDispatcher.java b/com/android/server/wm/PointerEventDispatcher.java
index 484987ec..ab8b8d47 100644
--- a/com/android/server/wm/PointerEventDispatcher.java
+++ b/com/android/server/wm/PointerEventDispatcher.java
@@ -21,7 +21,7 @@ import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.MotionEvent;
-import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
import com.android.server.UiThread;
diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java
index f541926e..4008811b 100644
--- a/com/android/server/wm/RootWindowContainer.java
+++ b/com/android/server/wm/RootWindowContainer.java
@@ -59,10 +59,10 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -138,7 +138,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
ParcelFileDescriptor mSurfaceTraceFd;
RemoteEventTrace mRemoteEventTrace;
- private final WindowLayersController mLayersController;
final WallpaperController mWallpaperController;
private final Handler mHandler;
@@ -163,7 +162,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
RootWindowContainer(WindowManagerService service) {
mService = service;
mHandler = new MyHandler(service.mH.getLooper());
- mLayersController = new WindowLayersController(mService);
mWallpaperController = new WallpaperController(mService);
}
@@ -231,7 +229,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
}
private DisplayContent createDisplayContent(final Display display) {
- final DisplayContent dc = new DisplayContent(display, mService, mLayersController,
+ final DisplayContent dc = new DisplayContent(display, mService,
mWallpaperController);
final int displayId = display.getDisplayId();
@@ -1103,4 +1101,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
String getName() {
return "ROOT";
}
+
+ @Override
+ void scheduleAnimation() {
+ mService.scheduleAnimationLocked();
+ }
}
diff --git a/com/android/server/wm/ScreenRotationAnimation.java b/com/android/server/wm/ScreenRotationAnimation.java
index 3350feae..5a39de5c 100644
--- a/com/android/server/wm/ScreenRotationAnimation.java
+++ b/com/android/server/wm/ScreenRotationAnimation.java
@@ -28,7 +28,6 @@ import static com.android.server.wm.proto.ScreenRotationAnimationProto.STARTED;
import android.content.Context;
import android.graphics.Matrix;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -225,12 +224,12 @@ class ScreenRotationAnimation {
}
public ScreenRotationAnimation(Context context, DisplayContent displayContent,
- SurfaceSession session, boolean inTransaction, boolean forceDefaultOrientation,
+ boolean inTransaction, boolean forceDefaultOrientation,
boolean isSecure, WindowManagerService service) {
mService = service;
mContext = context;
mDisplayContent = displayContent;
- displayContent.getLogicalDisplayRect(mOriginalDisplayRect);
+ displayContent.getBounds(mOriginalDisplayRect);
// Screenshot does NOT include rotation!
final Display display = displayContent.getDisplay();
@@ -269,7 +268,7 @@ class ScreenRotationAnimation {
try {
try {
- mSurfaceControl = new SurfaceControl.Builder(session)
+ mSurfaceControl = displayContent.makeOverlay()
.setName("ScreenshotSurface")
.setSize(mWidth, mHeight)
.setSecure(isSecure)
@@ -281,7 +280,6 @@ class ScreenRotationAnimation {
// TODO(multidisplay): we should use the proper display
SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);
- mSurfaceControl.setLayerStack(display.getLayerStack());
mSurfaceControl.setLayer(SCREEN_FREEZE_LAYER_SCREENSHOT);
mSurfaceControl.setAlpha(0);
mSurfaceControl.show();
@@ -313,7 +311,7 @@ class ScreenRotationAnimation {
float x = mTmpFloats[Matrix.MTRANS_X];
float y = mTmpFloats[Matrix.MTRANS_Y];
if (mForceDefaultOrientation) {
- mDisplayContent.getLogicalDisplayRect(mCurrentDisplayRect);
+ mDisplayContent.getBounds(mCurrentDisplayRect);
x -= mCurrentDisplayRect.left;
y -= mCurrentDisplayRect.top;
}
@@ -370,11 +368,11 @@ class ScreenRotationAnimation {
}
// Must be called while in a transaction.
- public boolean setRotationInTransaction(int rotation, SurfaceSession session,
+ public boolean setRotationInTransaction(int rotation,
long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) {
setRotationInTransaction(rotation);
if (TWO_PHASE_ANIMATION) {
- return startAnimation(session, maxAnimationDuration, animationScale,
+ return startAnimation(maxAnimationDuration, animationScale,
finalWidth, finalHeight, false, 0, 0);
}
@@ -385,7 +383,7 @@ class ScreenRotationAnimation {
/**
* Returns true if animating.
*/
- private boolean startAnimation(SurfaceSession session, long maxAnimationDuration,
+ private boolean startAnimation(long maxAnimationDuration,
float animationScale, int finalWidth, int finalHeight, boolean dismissing,
int exitAnim, int enterAnim) {
if (mSurfaceControl == null) {
@@ -561,8 +559,8 @@ class ScreenRotationAnimation {
Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1,
mOriginalWidth*2, mOriginalHeight*2);
Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
- mCustomBlackFrame = new BlackFrame(session, outer, inner,
- SCREEN_FREEZE_LAYER_CUSTOM, layerStack, false);
+ mCustomBlackFrame = new BlackFrame(outer, inner,
+ SCREEN_FREEZE_LAYER_CUSTOM, mDisplayContent, false);
mCustomBlackFrame.setMatrix(mFrameInitialMatrix);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
@@ -601,8 +599,8 @@ class ScreenRotationAnimation {
mOriginalWidth*2, mOriginalHeight*2);
inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
}
- mExitingBlackFrame = new BlackFrame(session, outer, inner,
- SCREEN_FREEZE_LAYER_EXIT, layerStack, mForceDefaultOrientation);
+ mExitingBlackFrame = new BlackFrame(outer, inner,
+ SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation);
mExitingBlackFrame.setMatrix(mFrameInitialMatrix);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
@@ -624,8 +622,8 @@ class ScreenRotationAnimation {
Rect outer = new Rect(-finalWidth*1, -finalHeight*1,
finalWidth*2, finalHeight*2);
Rect inner = new Rect(0, 0, finalWidth, finalHeight);
- mEnteringBlackFrame = new BlackFrame(session, outer, inner,
- SCREEN_FREEZE_LAYER_ENTER, layerStack, false);
+ mEnteringBlackFrame = new BlackFrame(outer, inner,
+ SCREEN_FREEZE_LAYER_ENTER, mDisplayContent, false);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
@@ -642,7 +640,7 @@ class ScreenRotationAnimation {
/**
* Returns true if animating.
*/
- public boolean dismiss(SurfaceSession session, long maxAnimationDuration,
+ public boolean dismiss(long maxAnimationDuration,
float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
if (DEBUG_STATE) Slog.v(TAG, "Dismiss!");
if (mSurfaceControl == null) {
@@ -650,7 +648,7 @@ class ScreenRotationAnimation {
return false;
}
if (!mStarted) {
- startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight,
+ startAnimation(maxAnimationDuration, animationScale, finalWidth, finalHeight,
true, exitAnim, enterAnim);
}
if (!mStarted) {
diff --git a/com/android/server/wm/SnapshotStartingData.java b/com/android/server/wm/SnapshotStartingData.java
index 35f35db5..c9e43c52 100644
--- a/com/android/server/wm/SnapshotStartingData.java
+++ b/com/android/server/wm/SnapshotStartingData.java
@@ -17,8 +17,8 @@
package com.android.server.wm;
import android.app.ActivityManager.TaskSnapshot;
-import android.graphics.GraphicBuffer;
-import android.view.WindowManagerPolicy.StartingSurface;
+
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
/**
* Represents starting data for snapshot starting windows.
diff --git a/com/android/server/wm/SplashScreenStartingData.java b/com/android/server/wm/SplashScreenStartingData.java
index 4b14f867..f52ce389 100644
--- a/com/android/server/wm/SplashScreenStartingData.java
+++ b/com/android/server/wm/SplashScreenStartingData.java
@@ -18,7 +18,8 @@ package com.android.server.wm;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.view.WindowManagerPolicy.StartingSurface;
+
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
/**
* Represents starting data for splash screens, i.e. "traditional" starting windows.
diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java
index 95c1d536..e7547bf0 100644
--- a/com/android/server/wm/StackWindowController.java
+++ b/com/android/server/wm/StackWindowController.java
@@ -87,12 +87,6 @@ public class StackWindowController
}
}
- public boolean isVisible() {
- synchronized (mWindowMap) {
- return mContainer != null && mContainer.isVisible();
- }
- }
-
public void reparent(int displayId, Rect outStackBounds, boolean onTop) {
synchronized (mWindowMap) {
if (mContainer == null) {
@@ -111,8 +105,7 @@ public class StackWindowController
}
}
- public void positionChildAt(TaskWindowContainerController child, int position, Rect bounds,
- Configuration overrideConfig) {
+ public void positionChildAt(TaskWindowContainerController child, int position) {
synchronized (mWindowMap) {
if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child
+ " at " + position);
@@ -126,7 +119,7 @@ public class StackWindowController
"positionChildAt: could not find stack for task=" + mContainer);
return;
}
- child.mContainer.positionAt(position, bounds, overrideConfig);
+ child.mContainer.positionAt(position);
mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
}
}
@@ -178,24 +171,22 @@ public class StackWindowController
* Re-sizes a stack and its containing tasks.
*
* @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
- * @param configs Configurations for tasks in the resized stack, keyed by task id.
* @param taskBounds Bounds for tasks in the resized stack, keyed by task id.
- * @return True if the stack is now fullscreen.
+ * @param taskTempInsetBounds Inset bounds for individual tasks, keyed by task id.
*/
- public boolean resize(Rect bounds, SparseArray<Configuration> configs,
- SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) {
+ public void resize(Rect bounds, SparseArray<Rect> taskBounds,
+ SparseArray<Rect> taskTempInsetBounds) {
synchronized (mWindowMap) {
if (mContainer == null) {
throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
}
// We might trigger a configuration change. Save the current task bounds for freezing.
mContainer.prepareFreezingTaskBounds();
- if (mContainer.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
+ if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds)
&& mContainer.isVisible()) {
mContainer.getDisplayContent().setLayoutNeeded();
mService.mWindowPlacerLocked.performSurfacePlacement();
}
- return mContainer.getRawFullscreen();
}
}
@@ -227,7 +218,7 @@ public class StackWindowController
public void getRawBounds(Rect outBounds) {
synchronized (mWindowMap) {
- if (mContainer.getRawFullscreen()) {
+ if (mContainer.matchParentBounds()) {
outBounds.setEmpty();
} else {
mContainer.getRawBounds(outBounds);
@@ -275,6 +266,7 @@ public class StackWindowController
final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
+ config.windowConfiguration.setBounds(bounds);
config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null);
boolean intersectParentBounds = false;
diff --git a/com/android/server/wm/StartingData.java b/com/android/server/wm/StartingData.java
index 8c564bb5..eb5011f7 100644
--- a/com/android/server/wm/StartingData.java
+++ b/com/android/server/wm/StartingData.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import android.view.WindowManagerPolicy.StartingSurface;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
/**
* Represents the model about how a starting window should be constructed.
diff --git a/com/android/server/wm/StrictModeFlash.java b/com/android/server/wm/StrictModeFlash.java
index eb8ee696..f51a6a92 100644
--- a/com/android/server/wm/StrictModeFlash.java
+++ b/com/android/server/wm/StrictModeFlash.java
@@ -41,15 +41,14 @@ class StrictModeFlash {
private boolean mDrawNeeded;
private final int mThickness = 20;
- public StrictModeFlash(Display display, SurfaceSession session) {
+ public StrictModeFlash(DisplayContent dc) {
SurfaceControl ctrl = null;
try {
- ctrl = new SurfaceControl.Builder(session)
+ ctrl = dc.makeOverlay()
.setName("StrictModeFlash")
.setSize(1, 1)
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- ctrl.setLayerStack(display.getLayerStack());
ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary.
ctrl.setPosition(0, 0);
ctrl.show();
diff --git a/com/android/server/wm/SurfaceBuilderFactory.java b/com/android/server/wm/SurfaceBuilderFactory.java
new file mode 100644
index 00000000..5390e5a1
--- /dev/null
+++ b/com/android/server/wm/SurfaceBuilderFactory.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.view.SurfaceSession;
+import android.view.SurfaceControl;
+
+interface SurfaceBuilderFactory {
+ SurfaceControl.Builder make(SurfaceSession s);
+};
+
diff --git a/com/android/server/wm/SurfaceControlWithBackground.java b/com/android/server/wm/SurfaceControlWithBackground.java
index a5080d57..7c5bd43a 100644
--- a/com/android/server/wm/SurfaceControlWithBackground.java
+++ b/com/android/server/wm/SurfaceControlWithBackground.java
@@ -16,7 +16,13 @@
package com.android.server.wm;
-import android.graphics.PixelFormat;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
+
import android.graphics.Rect;
import android.graphics.Region;
import android.os.IBinder;
@@ -24,13 +30,6 @@ import android.os.Parcel;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManagerPolicy.NAV_BAR_BOTTOM;
-import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
-import static android.view.WindowManagerPolicy.NAV_BAR_RIGHT;
/**
* SurfaceControl extension that has black background behind navigation bar area for fullscreen
diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java
index 13435d76..8aa129a4 100644
--- a/com/android/server/wm/Task.java
+++ b/com/android/server/wm/Task.java
@@ -51,14 +51,8 @@ import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
import java.util.function.Consumer;
-class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerUser {
+class Task extends WindowContainer<AppWindowToken> {
static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
- // Return value from {@link setBounds} indicating no change was made to the Task bounds.
- private static final int BOUNDS_CHANGE_NONE = 0;
- // Return value from {@link setBounds} indicating the position of the Task bounds changed.
- private static final int BOUNDS_CHANGE_POSITION = 1;
- // Return value from {@link setBounds} indicating the size of the Task bounds changed.
- private static final int BOUNDS_CHANGE_SIZE = 1 << 1;
// TODO: Track parent marks like this in WindowContainer.
TaskStack mStack;
@@ -67,8 +61,6 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
private boolean mDeferRemoval = false;
final WindowManagerService mService;
- // Content limits relative to the DisplayContent this sits in.
- private Rect mBounds = new Rect();
final Rect mPreparedFrozenBounds = new Rect();
final Configuration mPreparedFrozenMergedConfig = new Configuration();
@@ -78,13 +70,12 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
// Device rotation as of the last time {@link #mBounds} was set.
private int mRotation;
- // Whether mBounds is fullscreen
- private boolean mFillsParent = true;
-
// For comparison with DisplayContent bounds.
private Rect mTmpRect = new Rect();
// For handling display rotations.
private Rect mTmpRect2 = new Rect();
+ // For retrieving dim bounds
+ private Rect mTmpRect3 = new Rect();
// Resize mode of the task. See {@link ActivityInfo#resizeMode}
private int mResizeMode;
@@ -105,8 +96,11 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
// stack moves and we in fact do so when moving from full screen to pinned.
private boolean mPreserveNonFloatingState = false;
- Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
- int resizeMode, boolean supportsPictureInPicture, TaskDescription taskDescription,
+ private Dimmer mDimmer = new Dimmer(this);
+ private final Rect mTmpDimBoundsRect = new Rect();
+
+ Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode,
+ boolean supportsPictureInPicture, TaskDescription taskDescription,
TaskWindowContainerController controller) {
mTaskId = taskId;
mStack = stack;
@@ -115,7 +109,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
mResizeMode = resizeMode;
mSupportsPictureInPicture = supportsPictureInPicture;
setController(controller);
- setBounds(bounds, getOverrideConfiguration());
+ setBounds(getOverrideBounds());
mTaskDescription = taskDescription;
// Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
@@ -188,12 +182,6 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeTask");
mDeferRemoval = false;
- // Make sure to remove dim layer user first before removing task its from parent.
- DisplayContent content = getDisplayContent();
- if (content != null) {
- content.mDimLayerController.removeDimLayerUser(this);
- }
-
super.removeImmediately();
}
@@ -230,13 +218,14 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
}
/** @see com.android.server.am.ActivityManagerService#positionTaskInStack(int, int, int). */
- void positionAt(int position, Rect bounds, Configuration overrideConfig) {
+ void positionAt(int position) {
mStack.positionChildAt(position, this, false /* includingParents */);
- resizeLocked(bounds, overrideConfig, false /* force */);
}
@Override
void onParentSet() {
+ super.onParentSet();
+
// Update task bounds if needed.
updateDisplayInfo(getDisplayContent());
@@ -272,50 +261,37 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
}
}
- /** Set the task bounds. Passing in null sets the bounds to fullscreen. */
- // TODO: There is probably not a need to pass in overrideConfig anymore since any change to it
- // will be automatically propagated from the AM. Also, mBound is going to be in
- // WindowConfiguration long term.
- private int setBounds(Rect bounds, Configuration overrideConfig) {
- if (overrideConfig == null) {
- overrideConfig = EMPTY;
+ public int setBounds(Rect bounds, boolean forceResize) {
+ final int boundsChanged = setBounds(bounds);
+
+ if (forceResize && (boundsChanged & BOUNDS_CHANGE_SIZE) != BOUNDS_CHANGE_SIZE) {
+ onResize();
+ return BOUNDS_CHANGE_SIZE | boundsChanged;
}
- boolean oldFullscreen = mFillsParent;
+ return boundsChanged;
+ }
+
+ /** Set the task bounds. Passing in null sets the bounds to fullscreen. */
+ @Override
+ public int setBounds(Rect bounds) {
int rotation = Surface.ROTATION_0;
final DisplayContent displayContent = mStack.getDisplayContent();
if (displayContent != null) {
- displayContent.getLogicalDisplayRect(mTmpRect);
rotation = displayContent.getDisplayInfo().rotation;
- mFillsParent = bounds == null;
- if (mFillsParent) {
- bounds = mTmpRect;
- }
- }
-
- if (bounds == null) {
+ } else if (bounds == null) {
// Can't set to fullscreen if we don't have a display to get bounds from...
return BOUNDS_CHANGE_NONE;
}
- if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) {
- return BOUNDS_CHANGE_NONE;
- }
- int boundsChange = BOUNDS_CHANGE_NONE;
- if (mBounds.left != bounds.left || mBounds.top != bounds.top) {
- boundsChange |= BOUNDS_CHANGE_POSITION;
- }
- if (mBounds.width() != bounds.width() || mBounds.height() != bounds.height()) {
- boundsChange |= BOUNDS_CHANGE_SIZE;
+ if (equivalentOverrideBounds(bounds)) {
+ return BOUNDS_CHANGE_NONE;
}
- mBounds.set(bounds);
+ final int boundsChange = super.setBounds(bounds);
mRotation = rotation;
- if (displayContent != null) {
- displayContent.mDimLayerController.updateDimLayer(this);
- }
- onOverrideConfigurationChanged(overrideConfig);
+
return boundsChange;
}
@@ -363,28 +339,12 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
return isResizeable();
}
- boolean resizeLocked(Rect bounds, Configuration overrideConfig, boolean forced) {
- int boundsChanged = setBounds(bounds, overrideConfig);
- if (forced) {
- boundsChanged |= BOUNDS_CHANGE_SIZE;
- }
- if (boundsChanged == BOUNDS_CHANGE_NONE) {
- return false;
- }
- if ((boundsChanged & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
- onResize();
- } else {
- onMovedByResize();
- }
- return true;
- }
-
/**
* Prepares the task bounds to be frozen with the current size. See
* {@link AppWindowToken#freezeBounds}.
*/
void prepareFreezingBounds() {
- mPreparedFrozenBounds.set(mBounds);
+ mPreparedFrozenBounds.set(getBounds());
mPreparedFrozenMergedConfig.setTo(getConfiguration());
}
@@ -410,30 +370,30 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
mTmpRect2.offsetTo(adjustedBounds.left, adjustedBounds.top);
}
setTempInsetBounds(tempInsetBounds);
- resizeLocked(mTmpRect2, getOverrideConfiguration(), false /* forced */);
+ setBounds(mTmpRect2, false /* forced */);
}
/** Return true if the current bound can get outputted to the rest of the system as-is. */
private boolean useCurrentBounds() {
final DisplayContent displayContent = getDisplayContent();
- return mFillsParent
+ return matchParentBounds()
|| !inSplitScreenSecondaryWindowingMode()
|| displayContent == null
|| displayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null;
}
- /** Original bounds of the task if applicable, otherwise fullscreen rect. */
- void getBounds(Rect out) {
+ @Override
+ public void getBounds(Rect out) {
if (useCurrentBounds()) {
// No need to adjust the output bounds if fullscreen or the docked stack is visible
// since it is already what we want to represent to the rest of the system.
- out.set(mBounds);
+ super.getBounds(out);
return;
}
// The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
// not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
- mStack.getDisplayContent().getLogicalDisplayRect(out);
+ mStack.getDisplayContent().getBounds(out);
}
/**
@@ -482,7 +442,6 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
}
/** Bounds of the task to be used for dimming, as well as touch related tests. */
- @Override
public void getDimBounds(Rect out) {
final DisplayContent displayContent = mStack.getDisplayContent();
// It doesn't matter if we in particular are part of the resize, since we couldn't have
@@ -494,7 +453,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
return;
}
- if (!mFillsParent) {
+ if (!matchParentBounds()) {
// When minimizing the docked stack when going home, we don't adjust the task bounds
// so we need to intersect the task bounds with the stack bounds here.
//
@@ -505,11 +464,11 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
mStack.getBounds(out);
} else {
mStack.getBounds(mTmpRect);
- mTmpRect.intersect(mBounds);
+ mTmpRect.intersect(getBounds());
}
out.set(mTmpRect);
} else {
- out.set(mBounds);
+ out.set(getBounds());
}
return;
}
@@ -517,7 +476,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
// The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
// not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
if (displayContent != null) {
- displayContent.getLogicalDisplayRect(out);
+ displayContent.getBounds(out);
}
}
@@ -545,10 +504,10 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
if (displayContent == null) {
return;
}
- if (mFillsParent) {
+ if (matchParentBounds()) {
// TODO: Yeah...not sure if this works with WindowConfiguration, but shouldn't be a
// problem once we move mBounds into WindowConfiguration.
- setBounds(null, getOverrideConfiguration());
+ setBounds(null);
return;
}
final int newRotation = displayContent.getDisplayInfo().rotation;
@@ -561,18 +520,18 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
// task bounds so it stays in the same place.
// - Rotate the bounds and notify activity manager if the task can be resized independently
// from its stack. The stack will take care of task rotation for the other case.
- mTmpRect2.set(mBounds);
+ mTmpRect2.set(getBounds());
if (!getWindowConfiguration().canResizeTask()) {
- setBounds(mTmpRect2, getOverrideConfiguration());
+ setBounds(mTmpRect2);
return;
}
displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
- if (setBounds(mTmpRect2, getOverrideConfiguration()) != BOUNDS_CHANGE_NONE) {
+ if (setBounds(mTmpRect2) != BOUNDS_CHANGE_NONE) {
final TaskWindowContainerController controller = getController();
if (controller != null) {
- controller.requestResize(mBounds, RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
+ controller.requestResize(getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
}
}
}
@@ -634,26 +593,9 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
return null;
}
- @Override
- public boolean dimFullscreen() {
- return isFullscreen();
- }
-
- @Override
- public int getLayerForDim(WindowStateAnimator animator, int layerOffset, int defaultLayer) {
- // If the dim layer is for a starting window, move the dim layer back in the z-order behind
- // the lowest activity window to ensure it does not occlude the main window if it is
- // translucent
- final AppWindowToken appToken = animator.mWin.mAppToken;
- if (animator.mAttrType == TYPE_APPLICATION_STARTING && hasChild(appToken) ) {
- return Math.min(defaultLayer, appToken.getLowestAnimLayer() - layerOffset);
- }
- return defaultLayer;
- }
-
boolean isFullscreen() {
if (useCurrentBounds()) {
- return mFillsParent;
+ return matchParentBounds();
}
// The bounds has been adjusted to accommodate for a docked stack, but the docked stack
// is not currently visible. Go ahead a represent it as fullscreen to the rest of the
@@ -661,16 +603,6 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
return true;
}
- @Override
- public DisplayInfo getDisplayInfo() {
- return getDisplayContent().getDisplayInfo();
- }
-
- @Override
- public boolean isAttachedToDisplay() {
- return getDisplayContent() != null;
- }
-
void forceWindowsScaleable(boolean force) {
mService.openSurfaceTransaction();
try {
@@ -692,7 +624,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
@Override
boolean fillsParent() {
- return mFillsParent || !getWindowConfiguration().canResizeTask();
+ return matchParentBounds() || !getWindowConfiguration().canResizeTask();
}
@Override
@@ -718,9 +650,18 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
mPreserveNonFloatingState = false;
}
+ Dimmer getDimmer() {
+ return mDimmer;
+ }
+
@Override
- public String toShortString() {
- return "Task=" + mTaskId;
+ void prepareSurfaces() {
+ mDimmer.resetDimStates();
+ super.prepareSurfaces();
+ getDimBounds(mTmpDimBoundsRect);
+ if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+ scheduleAnimation();
+ }
}
@CallSuper
@@ -733,8 +674,8 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
final AppWindowToken appWindowToken = mChildren.get(i);
appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS, trim);
}
- proto.write(FILLS_PARENT, mFillsParent);
- mBounds.writeToProto(proto, BOUNDS);
+ proto.write(FILLS_PARENT, matchParentBounds());
+ getBounds().writeToProto(proto, BOUNDS);
mTempInsetBounds.writeToProto(proto, TEMP_INSET_BOUNDS);
proto.end(token);
}
@@ -743,8 +684,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
final String doublePrefix = prefix + " ";
pw.println(prefix + "taskId=" + mTaskId);
- pw.println(doublePrefix + "mFillsParent=" + mFillsParent);
- pw.println(doublePrefix + "mBounds=" + mBounds.toShortString());
+ pw.println(doublePrefix + "mBounds=" + getBounds().toShortString());
pw.println(doublePrefix + "mdr=" + mDeferRemoval);
pw.println(doublePrefix + "appTokens=" + mChildren);
pw.println(doublePrefix + "mTempInsetBounds=" + mTempInsetBounds.toShortString());
@@ -757,4 +697,8 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
wtoken.dump(pw, triplePrefix);
}
}
+
+ String toShortString() {
+ return "Task=" + mTaskId;
+ }
}
diff --git a/com/android/server/wm/TaskPositioner.java b/com/android/server/wm/TaskPositioner.java
index 12f6b5a2..87d0a401 100644
--- a/com/android/server/wm/TaskPositioner.java
+++ b/com/android/server/wm/TaskPositioner.java
@@ -59,7 +59,7 @@ import com.android.server.wm.WindowManagerService.H;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-class TaskPositioner implements DimLayer.DimLayerUser {
+class TaskPositioner {
private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
private static final String TAG_LOCAL = "TaskPositioner";
private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
@@ -99,9 +99,6 @@ class TaskPositioner implements DimLayer.DimLayerUser {
private WindowPositionerEventReceiver mInputEventReceiver;
private Display mDisplay;
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
- private DimLayer mDimLayer;
- @CtrlType
- private int mCurrentDimSide;
private Rect mTmpRect = new Rect();
private int mSideMargin;
private int mMinVisibleWidth;
@@ -207,15 +204,6 @@ class TaskPositioner implements DimLayer.DimLayerUser {
mService.mActivityManager.resizeTask(
mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
}
-
- if (mCurrentDimSide != CTRL_NONE) {
- final int createMode = mCurrentDimSide == CTRL_LEFT
- ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
- : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
- mService.mActivityManager.setTaskWindowingModeSplitScreenPrimary(
- mTask.mTaskId, createMode, true /*toTop*/, true /* animate */,
- null /* initialBounds */);
- }
} catch(RemoteException e) {}
// Post back to WM to handle clean-ups. We still need the input
@@ -243,7 +231,9 @@ class TaskPositioner implements DimLayer.DimLayerUser {
/**
* @param display The Display that the window being dragged is on.
*/
- void register(Display display) {
+ void register(DisplayContent displayContent) {
+ final Display display = displayContent.getDisplay();
+
if (DEBUG_TASK_POSITIONING) {
Slog.d(TAG, "Registering task positioner");
}
@@ -305,7 +295,6 @@ class TaskPositioner implements DimLayer.DimLayerUser {
}
mService.pauseRotationLocked();
- mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL);
mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
@@ -336,12 +325,6 @@ class TaskPositioner implements DimLayer.DimLayerUser {
mDragWindowHandle = null;
mDragApplicationHandle = null;
mDisplay = null;
-
- if (mDimLayer != null) {
- mDimLayer.destroySurface();
- mDimLayer = null;
- }
- mCurrentDimSide = CTRL_NONE;
mDragEnded = true;
// Resume rotations after a drag.
@@ -399,6 +382,27 @@ class TaskPositioner implements DimLayer.DimLayerUser {
mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
mWindowOriginalBounds.set(startBounds);
+ // Notify the app that resizing has started, even though we haven't received any new
+ // bounds yet. This will guarantee that the app starts the backdrop renderer before
+ // configuration changes which could cause an activity restart.
+ if (mResizing) {
+ synchronized (mService.mWindowMap) {
+ notifyMoveLocked(startX, startY);
+ }
+
+ // Perform the resize on the WMS handler thread when we don't have the WMS lock held
+ // to ensure that we don't deadlock WMS and AMS. Note that WindowPositionerEventReceiver
+ // callbacks are delivered on the same handler so this initial resize is always
+ // guaranteed to happen before subsequent drag resizes.
+ mService.mH.post(() -> {
+ try {
+ mService.mActivityManager.resizeTask(
+ mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED);
+ } catch (RemoteException e) {
+ }
+ });
+ }
+
// Make sure we always have valid drag bounds even if the drag ends before any move events
// have been handled.
mWindowDragBounds.set(startBounds);
@@ -434,7 +438,6 @@ class TaskPositioner implements DimLayer.DimLayerUser {
}
updateWindowDragBounds(nX, nY, mTmpRect);
- updateDimLayerVisibility(nX);
return false;
}
@@ -621,88 +624,6 @@ class TaskPositioner implements DimLayer.DimLayerUser {
"updateWindowDragBounds: " + mWindowDragBounds);
}
- private void updateDimLayerVisibility(int x) {
- @CtrlType
- int dimSide = getDimSide(x);
- if (dimSide == mCurrentDimSide) {
- return;
- }
-
- mCurrentDimSide = dimSide;
-
- if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility");
- mService.openSurfaceTransaction();
- if (mCurrentDimSide == CTRL_NONE) {
- mDimLayer.hide();
- } else {
- showDimLayer();
- }
- mService.closeSurfaceTransaction("updateDimLayerVisibility");
- }
-
- /**
- * Returns the side of the screen the dim layer should be shown.
- * @param x horizontal coordinate used to determine if the dim layer should be shown
- * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the
- * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer
- * shouldn't be shown.
- */
- private int getDimSide(int x) {
- if (!mTask.mStack.inFreeformWindowingMode()
- || !mTask.mStack.fillsParent()
- || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
- return CTRL_NONE;
- }
-
- mTask.mStack.getDimBounds(mTmpRect);
- if (x - mSideMargin <= mTmpRect.left) {
- return CTRL_LEFT;
- }
- if (x + mSideMargin >= mTmpRect.right) {
- return CTRL_RIGHT;
- }
-
- return CTRL_NONE;
- }
-
- private void showDimLayer() {
- mTask.mStack.getDimBounds(mTmpRect);
- if (mCurrentDimSide == CTRL_LEFT) {
- mTmpRect.right = mTmpRect.centerX();
- } else if (mCurrentDimSide == CTRL_RIGHT) {
- mTmpRect.left = mTmpRect.centerX();
- }
-
- mDimLayer.setBounds(mTmpRect);
- mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA,
- RESIZING_HINT_DURATION_MS);
- }
-
- @Override /** {@link DimLayer.DimLayerUser} */
- public boolean dimFullscreen() {
- return isFullscreen();
- }
-
- boolean isFullscreen() {
- return false;
- }
-
- @Override /** {@link DimLayer.DimLayerUser} */
- public DisplayInfo getDisplayInfo() {
- return mTask.mStack.getDisplayInfo();
- }
-
- @Override
- public boolean isAttachedToDisplay() {
- return mTask != null && mTask.getDisplayContent() != null;
- }
-
- @Override
- public void getDimBounds(Rect out) {
- // This dim layer user doesn't need this.
- }
-
- @Override
public String toShortString() {
return TAG;
}
diff --git a/com/android/server/wm/TaskSnapshotController.java b/com/android/server/wm/TaskSnapshotController.java
index 54ef0651..84e475a2 100644
--- a/com/android/server/wm/TaskSnapshotController.java
+++ b/com/android/server/wm/TaskSnapshotController.java
@@ -18,12 +18,12 @@ package com.android.server.wm;
import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
import android.app.ActivityManager.TaskSnapshot;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
@@ -35,16 +35,17 @@ import android.util.ArraySet;
import android.util.Slog;
import android.view.DisplayListCanvas;
import android.view.RenderNode;
+import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerPolicy.ScreenOffListener;
-import android.view.WindowManagerPolicy.StartingSurface;
-
-import com.google.android.collect.Sets;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
+import com.google.android.collect.Sets;
+
import java.io.PrintWriter;
/**
@@ -210,11 +211,28 @@ class TaskSnapshotController {
if (mainWindow == null) {
return null;
}
+ if (!mService.mPolicy.isScreenOn()) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
+ }
+ return null;
+ }
+ if (task.getSurfaceControl() == null) {
+ return null;
+ }
+
final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
- final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
- -1, -1, false, scaleFraction, false, true);
+ final Rect taskFrame = new Rect();
+ task.getBounds(taskFrame);
+
+ final GraphicBuffer buffer = SurfaceControl.captureLayersToBuffer(
+ task.getSurfaceControl().getHandle(), taskFrame, scaleFraction);
+
if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.w(TAG_WM, "Failed to take screenshot");
+ }
return null;
}
return new TaskSnapshot(buffer, top.getConfiguration().orientation,
diff --git a/com/android/server/wm/TaskSnapshotSurface.java b/com/android/server/wm/TaskSnapshotSurface.java
index 3ce090ac..41915a32 100644
--- a/com/android/server/wm/TaskSnapshotSurface.java
+++ b/com/android/server/wm/TaskSnapshotSurface.java
@@ -18,9 +18,7 @@ package com.android.server.wm;
import static android.graphics.Color.WHITE;
import static android.graphics.Color.alpha;
-import static android.view.SurfaceControl.HIDDEN;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
@@ -67,12 +65,12 @@ import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.WindowManagerPolicy.StartingSurface;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.DecorView;
import com.android.internal.view.BaseIWindow;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
/**
* This class represents a starting window that shows a snapshot.
diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java
index 053fb470..4a3a3fc9 100644
--- a/com/android/server/wm/TaskStack.java
+++ b/com/android/server/wm/TaskStack.java
@@ -31,12 +31,11 @@ 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 static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
import static com.android.server.wm.proto.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING;
import static com.android.server.wm.proto.StackProto.BOUNDS;
import static com.android.server.wm.proto.StackProto.FILLS_PARENT;
@@ -55,6 +54,7 @@ import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.Surface;
+import android.view.SurfaceControl;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
@@ -63,7 +63,7 @@ import com.android.server.EventLogTags;
import java.io.PrintWriter;
-public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLayerUser,
+public class TaskStack extends WindowContainer<Task> implements
BoundsAnimationTarget {
/** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
* restrict IME adjustment so that a min portion of top stack remains visible.*/
@@ -87,9 +87,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
private Rect mTmpRect2 = new Rect();
private Rect mTmpRect3 = new Rect();
- /** Content limits relative to the DisplayContent this sits in. */
- private Rect mBounds = new Rect();
-
/** Stack bounds adjusted to screen content area (taking into account IM windows, etc.) */
private final Rect mAdjustedBounds = new Rect();
@@ -99,17 +96,14 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
*/
private final Rect mFullyAdjustedImeBounds = new Rect();
- /** Whether mBounds is fullscreen */
- private boolean mFillsParent = true;
-
// Device rotation as of the last time {@link #mBounds} was set.
private int mRotation;
/** Density as of last time {@link #mBounds} was set. */
private int mDensity;
- /** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */
- private DimLayer mAnimationBackgroundSurface;
+ private SurfaceControl mAnimationBackgroundSurface;
+ private boolean mAnimationBackgroundSurfaceIsShown = false;
/** The particular window with an Animation with non-zero background color. */
private WindowStateAnimator mAnimationBackgroundAnimator;
@@ -149,6 +143,13 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
Rect mPreAnimationBounds = new Rect();
+ private Dimmer mDimmer = new Dimmer(this);
+
+ /**
+ * For {@link #prepareSurfaces}.
+ */
+ final Rect mTmpDimBoundsRect = new Rect();
+
TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
mService = service;
mStackId = stackId;
@@ -172,27 +173,20 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
/**
* Set the bounds of the stack and its containing tasks.
* @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen.
- * @param configs Configuration for individual tasks, keyed by task id.
* @param taskBounds Bounds for individual tasks, keyed by task id.
+ * @param taskTempInsetBounds Inset bounds for individual tasks, keyed by task id.
* @return True if the stack bounds was changed.
* */
boolean setBounds(
- Rect stackBounds, SparseArray<Configuration> configs, SparseArray<Rect> taskBounds,
- SparseArray<Rect> taskTempInsetBounds) {
+ Rect stackBounds, SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) {
setBounds(stackBounds);
// Update bounds of containing tasks.
for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
final Task task = mChildren.get(taskNdx);
- Configuration config = configs.get(task.mTaskId);
- if (config != null) {
- Rect bounds = taskBounds.get(task.mTaskId);
- task.resizeLocked(bounds, config, false /* forced */);
- task.setTempInsetBounds(taskTempInsetBounds != null ?
- taskTempInsetBounds.get(task.mTaskId) : null);
- } else {
- Slog.wtf(TAG_WM, "No config for task: " + task + ", is there a mismatch with AM?");
- }
+ task.setBounds(taskBounds.get(task.mTaskId), false /* forced */);
+ task.setTempInsetBounds(taskTempInsetBounds != null ?
+ taskTempInsetBounds.get(task.mTaskId) : null);
}
return true;
}
@@ -219,20 +213,20 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
final boolean adjusted = !mAdjustedBounds.isEmpty();
Rect insetBounds = null;
if (adjusted && isAdjustedForMinimizedDockedStack()) {
- insetBounds = mBounds;
+ insetBounds = getRawBounds();
} else if (adjusted && mAdjustedForIme) {
if (mImeGoingAway) {
- insetBounds = mBounds;
+ insetBounds = getRawBounds();
} else {
insetBounds = mFullyAdjustedImeBounds;
}
}
- alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : mBounds, insetBounds);
+ alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : getRawBounds(), insetBounds);
mDisplayContent.setLayoutNeeded();
}
private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
- if (mFillsParent) {
+ if (matchParentBounds()) {
return;
}
@@ -245,53 +239,85 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
}
- private boolean setBounds(Rect bounds) {
- boolean oldFullscreen = mFillsParent;
+ private void updateAnimationBackgroundBounds() {
+ if (mAnimationBackgroundSurface == null) {
+ return;
+ }
+ getRawBounds(mTmpRect);
+ // TODO: Should be in relative coordinates.
+ getPendingTransaction().setSize(mAnimationBackgroundSurface, mTmpRect.width(),
+ mTmpRect.height()).setPosition(mAnimationBackgroundSurface, mTmpRect.left,
+ mTmpRect.top);
+ scheduleAnimation();
+ }
+
+ private void hideAnimationSurface() {
+ if (mAnimationBackgroundSurface == null) {
+ return;
+ }
+ getPendingTransaction().hide(mAnimationBackgroundSurface);
+ mAnimationBackgroundSurfaceIsShown = false;
+ scheduleAnimation();
+ }
+
+ private void showAnimationSurface(float alpha) {
+ if (mAnimationBackgroundSurface == null) {
+ return;
+ }
+ getPendingTransaction().setLayer(mAnimationBackgroundSurface, Integer.MIN_VALUE)
+ .setAlpha(mAnimationBackgroundSurface, alpha)
+ .show(mAnimationBackgroundSurface);
+ mAnimationBackgroundSurfaceIsShown = true;
+ scheduleAnimation();
+ }
+
+ @Override
+ public int setBounds(Rect bounds) {
+ return setBounds(getOverrideBounds(), bounds);
+ }
+
+ private int setBounds(Rect existing, Rect bounds) {
int rotation = Surface.ROTATION_0;
int density = DENSITY_DPI_UNDEFINED;
if (mDisplayContent != null) {
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect);
rotation = mDisplayContent.getDisplayInfo().rotation;
density = mDisplayContent.getDisplayInfo().logicalDensityDpi;
- mFillsParent = bounds == null;
- if (mFillsParent) {
- bounds = mTmpRect;
- }
}
- if (bounds == null) {
- // Can't set to fullscreen if we don't have a display to get bounds from...
- return false;
- }
- if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) {
- return false;
+ if (equivalentBounds(existing, bounds) && mRotation == rotation) {
+ return BOUNDS_CHANGE_NONE;
}
+ final int result = super.setBounds(bounds);
+
if (mDisplayContent != null) {
- mDisplayContent.mDimLayerController.updateDimLayer(this);
- mAnimationBackgroundSurface.setBounds(bounds);
+ updateAnimationBackgroundBounds();
}
- mBounds.set(bounds);
mRotation = rotation;
mDensity = density;
updateAdjustedBounds();
- return true;
+ return result;
}
/** Bounds of the stack without adjusting for other factors in the system like visibility
* of docked stack.
- * Most callers should be using {@link #getBounds} as it take into consideration other system
- * factors. */
+ * Most callers should be using {@link ConfigurationContainer#getOverrideBounds} as it take into
+ * consideration other system factors. */
void getRawBounds(Rect out) {
- out.set(mBounds);
+ out.set(getRawBounds());
+ }
+
+ Rect getRawBounds() {
+ return super.getBounds();
}
/** Return true if the current bound can get outputted to the rest of the system as-is. */
private boolean useCurrentBounds() {
- if (mFillsParent
+ if (matchParentBounds()
|| !inSplitScreenSecondaryWindowingMode()
|| mDisplayContent == null
|| mDisplayContent.getSplitScreenPrimaryStack() != null) {
@@ -300,24 +326,29 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
return false;
}
- public void getBounds(Rect out) {
+ @Override
+ public void getBounds(Rect bounds) {
+ bounds.set(getBounds());
+ }
+
+ @Override
+ public Rect getBounds() {
if (useCurrentBounds()) {
// If we're currently adjusting for IME or minimized docked stack, we use the adjusted
// bounds; otherwise, no need to adjust the output bounds if fullscreen or the docked
// stack is visible since it is already what we want to represent to the rest of the
// system.
if (!mAdjustedBounds.isEmpty()) {
- out.set(mAdjustedBounds);
+ return mAdjustedBounds;
} else {
- out.set(mBounds);
+ return super.getBounds();
}
- return;
}
// The bounds has been adjusted to accommodate for a docked stack, but the docked stack
// is not currently visible. Go ahead a represent it as fullscreen to the rest of the
// system.
- mDisplayContent.getLogicalDisplayRect(out);
+ return mDisplayContent.getBounds();
}
/**
@@ -338,7 +369,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
mBoundsAnimationSourceHintBounds.setEmpty();
}
- mPreAnimationBounds.set(mBounds);
+ mPreAnimationBounds.set(getRawBounds());
}
/**
@@ -368,7 +399,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
/** Bounds of the stack with other system factors taken into consideration. */
- @Override
public void getDimBounds(Rect out) {
getBounds(out);
}
@@ -384,12 +414,12 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
if (bounds != null) {
setBounds(bounds);
return;
- } else if (mFillsParent) {
+ } else if (matchParentBounds()) {
setBounds(null);
return;
}
- mTmpRect2.set(mBounds);
+ mTmpRect2.set(getRawBounds());
final int newRotation = mDisplayContent.getDisplayInfo().rotation;
final int newDensity = mDisplayContent.getDisplayInfo().logicalDensityDpi;
if (mRotation == newRotation && mDensity == newDensity) {
@@ -432,14 +462,14 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
return false;
}
- if (mFillsParent) {
+ if (matchParentBounds()) {
// Update stack bounds again since rotation changed since updateDisplayInfo().
setBounds(null);
// Return false since we don't need the client to resize.
return false;
}
- mTmpRect2.set(mBounds);
+ mTmpRect2.set(getRawBounds());
mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
if (inSplitScreenPrimaryWindowingMode()) {
repositionPrimarySplitScreenStackAfterRotation(mTmpRect2);
@@ -476,7 +506,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
if (mDisplayContent.getDockedDividerController().canPrimaryStackDockTo(dockSide)) {
return;
}
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect);
dockSide = DockedDividerUtils.invertDockSide(dockSide);
switch (dockSide) {
case DOCKED_LEFT:
@@ -700,9 +730,12 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
mDisplayContent = dc;
- mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(),
- "animation background stackId=" + mStackId);
+
updateBoundsForWindowModeChange();
+ mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer(true)
+ .setName("animation background stackId=" + mStackId)
+ .build();
+
super.onDisplayChanged(dc);
}
@@ -718,7 +751,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
// not fullscreen. If it's fullscreen, it means that we are in the transition of
// dismissing it, so we must not resize this stack.
bounds = new Rect();
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect);
mTmpRect2.setEmpty();
if (splitScreenStack != null) {
splitScreenStack.getRawBounds(mTmpRect2);
@@ -781,7 +814,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
if (!inSplitScreenWindowingMode() || mDisplayContent == null) {
- outStackBounds.set(mBounds);
+ outStackBounds.set(getRawBounds());
return;
}
@@ -796,7 +829,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
// The docked stack is being dismissed, but we caught before it finished being
// dismissed. In that case we want to treat it as if it is not occupying any space and
// let others occupy the whole display.
- mDisplayContent.getLogicalDisplayRect(outStackBounds);
+ mDisplayContent.getBounds(outStackBounds);
return;
}
@@ -804,11 +837,11 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
if (dockedSide == DOCKED_INVALID) {
// Not sure how you got here...Only thing we can do is return current bounds.
Slog.e(TAG_WM, "Failed to get valid docked side for docked stack=" + dockedStack);
- outStackBounds.set(mBounds);
+ outStackBounds.set(getRawBounds());
return;
}
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect);
dockedStack.getRawBounds(mTmpRect2);
final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT;
getStackDockedModeBounds(mTmpRect, outStackBounds, mTmpRect2,
@@ -914,16 +947,16 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
@Override
void onParentSet() {
+ super.onParentSet();
+
if (getParent() != null || mDisplayContent == null) {
return;
}
- // 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);
if (mAnimationBackgroundSurface != null) {
- mAnimationBackgroundSurface.destroySurface();
+ mAnimationBackgroundSurface.destroy();
mAnimationBackgroundSurface = null;
}
@@ -933,9 +966,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
void resetAnimationBackgroundAnimator() {
mAnimationBackgroundAnimator = null;
- if (mAnimationBackgroundSurface != null) {
- mAnimationBackgroundSurface.hide();
- }
+ hideAnimationSurface();
}
void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
@@ -944,8 +975,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
|| animLayer < mAnimationBackgroundAnimator.mAnimLayer) {
mAnimationBackgroundAnimator = winAnimator;
animLayer = mDisplayContent.getLayerForAnimationBackground(winAnimator);
- mAnimationBackgroundSurface.show(animLayer - LAYER_OFFSET_DIM,
- ((color >> 24) & 0xff) / 255f, 0);
+ showAnimationSurface(((color >> 24) & 0xff) / 255f);
}
}
@@ -1112,14 +1142,14 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
// occluded by IME. We shift its bottom up by the height of the IME, but
// leaves at least 30% of the top stack visible.
final int minTopStackBottom =
- getMinTopStackBottom(displayContentRect, mBounds.bottom);
+ getMinTopStackBottom(displayContentRect, getRawBounds().bottom);
final int bottom = Math.max(
- mBounds.bottom - yOffset + dividerWidth - dividerWidthInactive,
+ getRawBounds().bottom - yOffset + dividerWidth - dividerWidthInactive,
minTopStackBottom);
- mTmpAdjustedBounds.set(mBounds);
- mTmpAdjustedBounds.bottom =
- (int) (mAdjustImeAmount * bottom + (1 - mAdjustImeAmount) * mBounds.bottom);
- mFullyAdjustedImeBounds.set(mBounds);
+ mTmpAdjustedBounds.set(getRawBounds());
+ mTmpAdjustedBounds.bottom = (int) (mAdjustImeAmount * bottom + (1 - mAdjustImeAmount)
+ * getRawBounds().bottom);
+ mFullyAdjustedImeBounds.set(getRawBounds());
} else {
// When the stack is on bottom and has no focus, it's only adjusted for divider width.
final int dividerWidthDelta = dividerWidthInactive - dividerWidth;
@@ -1129,22 +1159,24 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
// We try to move it up by the height of the IME window, but only to the extent
// that leaves at least 30% of the top stack visible.
// 'top' is where the top of bottom stack will move to in this case.
- final int topBeforeImeAdjust = mBounds.top - dividerWidth + dividerWidthInactive;
+ final int topBeforeImeAdjust =
+ getRawBounds().top - dividerWidth + dividerWidthInactive;
final int minTopStackBottom =
- getMinTopStackBottom(displayContentRect, mBounds.top - dividerWidth);
+ getMinTopStackBottom(displayContentRect,
+ getRawBounds().top - dividerWidth);
final int top = Math.max(
- mBounds.top - yOffset, minTopStackBottom + dividerWidthInactive);
+ getRawBounds().top - yOffset, minTopStackBottom + dividerWidthInactive);
- mTmpAdjustedBounds.set(mBounds);
+ mTmpAdjustedBounds.set(getRawBounds());
// Account for the adjustment for IME and divider width separately.
// (top - topBeforeImeAdjust) is the amount of movement due to IME only,
// and dividerWidthDelta is due to divider width change only.
- mTmpAdjustedBounds.top = mBounds.top +
+ mTmpAdjustedBounds.top = getRawBounds().top +
(int) (mAdjustImeAmount * (top - topBeforeImeAdjust) +
mAdjustDividerAmount * dividerWidthDelta);
- mFullyAdjustedImeBounds.set(mBounds);
+ mFullyAdjustedImeBounds.set(getRawBounds());
mFullyAdjustedImeBounds.top = top;
- mFullyAdjustedImeBounds.bottom = top + mBounds.height();
+ mFullyAdjustedImeBounds.bottom = top + getRawBounds().height();
}
return true;
}
@@ -1158,21 +1190,21 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
if (dockSide == DOCKED_TOP) {
mService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect);
int topInset = mTmpRect.top;
- mTmpAdjustedBounds.set(mBounds);
- mTmpAdjustedBounds.bottom =
- (int) (minimizeAmount * topInset + (1 - minimizeAmount) * mBounds.bottom);
+ mTmpAdjustedBounds.set(getRawBounds());
+ mTmpAdjustedBounds.bottom = (int) (minimizeAmount * topInset + (1 - minimizeAmount)
+ * getRawBounds().bottom);
} else if (dockSide == DOCKED_LEFT) {
- mTmpAdjustedBounds.set(mBounds);
- final int width = mBounds.width();
+ mTmpAdjustedBounds.set(getRawBounds());
+ final int width = getRawBounds().width();
mTmpAdjustedBounds.right =
(int) (minimizeAmount * mDockedStackMinimizeThickness
- + (1 - minimizeAmount) * mBounds.right);
+ + (1 - minimizeAmount) * getRawBounds().right);
mTmpAdjustedBounds.left = mTmpAdjustedBounds.right - width;
} else if (dockSide == DOCKED_RIGHT) {
- mTmpAdjustedBounds.set(mBounds);
- mTmpAdjustedBounds.left =
- (int) (minimizeAmount * (mBounds.right - mDockedStackMinimizeThickness)
- + (1 - minimizeAmount) * mBounds.left);
+ mTmpAdjustedBounds.set(getRawBounds());
+ mTmpAdjustedBounds.left = (int) (minimizeAmount *
+ (getRawBounds().right - mDockedStackMinimizeThickness)
+ + (1 - minimizeAmount) * getRawBounds().left);
}
return true;
}
@@ -1194,9 +1226,9 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
if (dockSide == DOCKED_TOP) {
mService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect);
int topInset = mTmpRect.top;
- return mBounds.bottom - topInset;
+ return getRawBounds().bottom - topInset;
} else if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
- return mBounds.width() - mDockedStackMinimizeThickness;
+ return getRawBounds().width() - mDockedStackMinimizeThickness;
} else {
return 0;
}
@@ -1230,11 +1262,12 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
return;
}
- final Rect insetBounds = mImeGoingAway ? mBounds : mFullyAdjustedImeBounds;
+ final Rect insetBounds = mImeGoingAway ? getRawBounds() : mFullyAdjustedImeBounds;
task.alignToAdjustedBounds(mAdjustedBounds, insetBounds, getDockSide() == DOCKED_TOP);
mDisplayContent.setLayoutNeeded();
}
+
boolean isAdjustedForMinimizedDockedStack() {
return mMinimizeAmount != 0f;
}
@@ -1248,17 +1281,16 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
mChildren.get(taskNdx).writeToProto(proto, TASKS, trim);
}
- proto.write(FILLS_PARENT, mFillsParent);
- mBounds.writeToProto(proto, BOUNDS);
- proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurface.isDimming());
+ proto.write(FILLS_PARENT, matchParentBounds());
+ getRawBounds().writeToProto(proto, BOUNDS);
+ proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurfaceIsShown);
proto.end(token);
}
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "mStackId=" + mStackId);
pw.println(prefix + "mDeferRemoval=" + mDeferRemoval);
- pw.println(prefix + "mFillsParent=" + mFillsParent);
- pw.println(prefix + "mBounds=" + mBounds.toShortString());
+ pw.println(prefix + "mBounds=" + getRawBounds().toShortString());
if (mMinimizeAmount != 0f) {
pw.println(prefix + "mMinimizeAmount=" + mMinimizeAmount);
}
@@ -1273,9 +1305,8 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
mChildren.get(taskNdx).dump(prefix + " ", pw);
}
- if (mAnimationBackgroundSurface.isDimming()) {
- pw.println(prefix + "mWindowAnimationBackgroundSurface:");
- mAnimationBackgroundSurface.printTo(prefix + " ", pw);
+ if (mAnimationBackgroundSurfaceIsShown) {
+ pw.println(prefix + "mWindowAnimationBackgroundSurface is shown");
}
if (!mExitingAppTokens.isEmpty()) {
pw.println();
@@ -1290,23 +1321,10 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
}
- /** Fullscreen status of the stack without adjusting for other factors in the system like
- * visibility of docked stack.
- * Most callers should be using {@link #fillsParent} as it take into consideration other
- * system factors. */
- boolean getRawFullscreen() {
- return mFillsParent;
- }
-
- @Override
- public boolean dimFullscreen() {
- return !isActivityTypeStandard() || fillsParent();
- }
-
@Override
boolean fillsParent() {
if (useCurrentBounds()) {
- return mFillsParent;
+ return matchParentBounds();
}
// The bounds has been adjusted to accommodate for a docked stack, but the docked stack
// is not currently visible. Go ahead a represent it as fullscreen to the rest of the
@@ -1315,16 +1333,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
}
@Override
- public DisplayInfo getDisplayInfo() {
- return mDisplayContent.getDisplayInfo();
- }
-
- @Override
- public boolean isAttachedToDisplay() {
- return mDisplayContent != null;
- }
-
- @Override
public String toString() {
return "{stackId=" + mStackId + " tasks=" + mChildren + "}";
}
@@ -1333,7 +1341,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
return toShortString();
}
- @Override
public String toShortString() {
return "Stack=" + mStackId;
}
@@ -1343,7 +1350,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
* information which side of the screen was the dock anchored.
*/
int getDockSide() {
- return getDockSide(mBounds);
+ return getDockSide(getRawBounds());
}
private int getDockSide(Rect bounds) {
@@ -1353,7 +1360,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
if (mDisplayContent == null) {
return DOCKED_INVALID;
}
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect);
final int orientation = mDisplayContent.getConfiguration().orientation;
return getDockSideUnchecked(bounds, mTmpRect, orientation);
}
@@ -1473,7 +1480,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
*/
if (task.isActivityTypeHome() && isMinimizedDockAndHomeStackResizable()) {
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect);
} else {
task.getDimBounds(mTmpRect);
}
@@ -1691,4 +1698,32 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
|| activityType == ACTIVITY_TYPE_RECENTS
|| activityType == ACTIVITY_TYPE_ASSISTANT;
}
+
+ Dimmer getDimmer() {
+ return mDimmer;
+ }
+
+ @Override
+ void prepareSurfaces() {
+ mDimmer.resetDimStates();
+ super.prepareSurfaces();
+ getDimBounds(mTmpDimBoundsRect);
+ if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+ scheduleAnimation();
+ }
+ }
+
+ public DisplayInfo getDisplayInfo() {
+ return mDisplayContent.getDisplayInfo();
+ }
+
+ void dim(float alpha) {
+ mDimmer.dimAbove(getPendingTransaction(), alpha);
+ scheduleAnimation();
+ }
+
+ void stopDimming() {
+ mDimmer.stopDim(getPendingTransaction());
+ scheduleAnimation();
+ }
}
diff --git a/com/android/server/wm/TaskTapPointerEventListener.java b/com/android/server/wm/TaskTapPointerEventListener.java
index 42a2d9df..84ad5764 100644
--- a/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/com/android/server/wm/TaskTapPointerEventListener.java
@@ -20,7 +20,7 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.view.MotionEvent;
-import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
import com.android.server.wm.WindowManagerService.H;
diff --git a/com/android/server/wm/TaskWindowContainerController.java b/com/android/server/wm/TaskWindowContainerController.java
index b3bb0b7e..5caae32d 100644
--- a/com/android/server/wm/TaskWindowContainerController.java
+++ b/com/android/server/wm/TaskWindowContainerController.java
@@ -18,7 +18,6 @@ package com.android.server.wm;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityManager.TaskSnapshot;
-import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
@@ -30,6 +29,7 @@ import com.android.internal.annotations.VisibleForTesting;
import java.lang.ref.WeakReference;
import static com.android.server.EventLogTags.WM_TASK_CREATED;
+import static com.android.server.wm.ConfigurationContainer.BOUNDS_CHANGE_NONE;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -75,7 +75,7 @@ public class TaskWindowContainerController
+ stackController);
}
EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
- final Task task = createTask(taskId, stack, userId, bounds, resizeMode,
+ final Task task = createTask(taskId, stack, userId, resizeMode,
supportsPictureInPicture, taskDescription);
final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
// We only want to move the parents to the parents if we are creating this task at the
@@ -85,10 +85,10 @@ public class TaskWindowContainerController
}
@VisibleForTesting
- Task createTask(int taskId, TaskStack stack, int userId, Rect bounds, int resizeMode,
+ Task createTask(int taskId, TaskStack stack, int userId, int resizeMode,
boolean supportsPictureInPicture, TaskDescription taskDescription) {
- return new Task(taskId, stack, userId, mService, bounds, resizeMode,
- supportsPictureInPicture, taskDescription, this);
+ return new Task(taskId, stack, userId, mService, resizeMode, supportsPictureInPicture,
+ taskDescription, this);
}
@Override
@@ -151,14 +151,14 @@ public class TaskWindowContainerController
}
}
- public void resize(Rect bounds, Configuration overrideConfig, boolean relayout,
- boolean forced) {
+ public void resize(boolean relayout, boolean forced) {
synchronized (mWindowMap) {
if (mContainer == null) {
throw new IllegalArgumentException("resizeTask: taskId " + mTaskId + " not found.");
}
- if (mContainer.resizeLocked(bounds, overrideConfig, forced) && relayout) {
+ if (mContainer.setBounds(mContainer.getOverrideBounds(), forced) != BOUNDS_CHANGE_NONE
+ && relayout) {
mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
}
}
diff --git a/com/android/server/wm/WallpaperController.java b/com/android/server/wm/WallpaperController.java
index 629cc868..3ae45497 100644
--- a/com/android/server/wm/WallpaperController.java
+++ b/com/android/server/wm/WallpaperController.java
@@ -23,8 +23,8 @@ 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;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
diff --git a/com/android/server/wm/Watermark.java b/com/android/server/wm/Watermark.java
index d97aaac4..9216b66e 100644
--- a/com/android/server/wm/Watermark.java
+++ b/com/android/server/wm/Watermark.java
@@ -53,7 +53,7 @@ class Watermark {
private int mLastDH;
private boolean mDrawNeeded;
- Watermark(Display display, DisplayMetrics dm, SurfaceSession session, String[] tokens) {
+ Watermark(DisplayContent dc, DisplayMetrics dm, String[] tokens) {
if (false) {
Log.i(TAG_WM, "*********************** WATERMARK");
for (int i=0; i<tokens.length; i++) {
@@ -61,7 +61,7 @@ class Watermark {
}
}
- mDisplay = display;
+ mDisplay = dc.getDisplay();
mTokens = tokens;
StringBuilder builder = new StringBuilder(32);
@@ -114,7 +114,7 @@ class Watermark {
SurfaceControl ctrl = null;
try {
- ctrl = new SurfaceControl.Builder(session)
+ ctrl = dc.makeOverlay()
.setName("WatermarkSurface")
.setSize(1, 1)
.setFormat(PixelFormat.TRANSLUCENT)
diff --git a/com/android/server/wm/WindowAnimator.java b/com/android/server/wm/WindowAnimator.java
index 1912095c..7c56f00b 100644
--- a/com/android/server/wm/WindowAnimator.java
+++ b/com/android/server/wm/WindowAnimator.java
@@ -30,10 +30,9 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.view.Choreographer;
-import android.view.SurfaceControl;
-import android.view.WindowManagerPolicy;
import com.android.server.AnimationThread;
+import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
@@ -200,7 +199,7 @@ public class WindowAnimator {
++mAnimTransactionSequence;
dc.updateWindowsForAnimator(this);
dc.updateWallpaperForAnimator(this);
- dc.prepareWindowSurfaces();
+ dc.prepareSurfaces();
}
for (int i = 0; i < numDisplays; i++) {
@@ -214,8 +213,6 @@ public class WindowAnimator {
if (screenRotationAnimation != null) {
screenRotationAnimation.updateSurfacesInTransaction();
}
-
- orAnimating(dc.animateDimLayers());
orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
//TODO (multidisplay): Magnification is supported only for the default display.
if (accessibilityController != null && dc.isDefaultDisplay) {
@@ -237,6 +234,13 @@ public class WindowAnimator {
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
}
+ final int numDisplays = mDisplayContentsAnimators.size();
+ for (int i = 0; i < numDisplays; i++) {
+ final int displayId = mDisplayContentsAnimators.keyAt(i);
+ final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+ dc.onPendingTransactionApplied();
+ }
+
boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
boolean doRequest = false;
if (mBulkUpdateParams != 0) {
@@ -271,6 +275,7 @@ public class WindowAnimator {
mService.destroyPreservedSurfaceLocked();
mService.mWindowPlacerLocked.destroyPendingSurfaces();
+
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
+ " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java
index 8f4b897c..64675825 100644
--- a/com/android/server/wm/WindowContainer.java
+++ b/com/android/server/wm/WindowContainer.java
@@ -21,9 +21,13 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static com.android.server.wm.proto.WindowContainerProto.CONFIGURATION_CONTAINER;
import static com.android.server.wm.proto.WindowContainerProto.ORIENTATION;
+import static android.view.SurfaceControl.Transaction;
import android.annotation.CallSuper;
import android.content.res.Configuration;
+import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
import android.util.Pools;
import android.util.proto.ProtoOutputStream;
@@ -64,7 +68,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
new Pools.SynchronizedPool<>(3);
// The owner/creator for this container. No controller if null.
- private WindowContainerController mController;
+ WindowContainerController mController;
+
+ protected SurfaceControl mSurfaceControl;
+
+ /**
+ * Applied as part of the animation pass in "prepareSurfaces".
+ */
+ private Transaction mPendingTransaction = new Transaction();
@Override
final protected WindowContainer getParent() {
@@ -101,7 +112,22 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* Supposed to be overridden and contain actions that should be executed after parent was set.
*/
void onParentSet() {
- // Do nothing by default.
+ if (mParent == null) {
+ return;
+ }
+ if (mSurfaceControl == null) {
+ // If we don't yet have a surface, but we now have a parent, we should
+ // build a surface.
+ mSurfaceControl = makeSurface().build();
+ getPendingTransaction().show(mSurfaceControl);
+ } else {
+ // If we have a surface but a new parent, we just need to perform a reparent.
+ getPendingTransaction().reparent(mSurfaceControl, mParent.mSurfaceControl.getHandle());
+ }
+
+ // Either way we need to ask the parent to assign us a Z-order.
+ mParent.assignChildLayers();
+ scheduleAnimation();
}
// Temp. holders for a chain of containers we are currently processing.
@@ -188,6 +214,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mChildren.remove(child);
}
+ if (mSurfaceControl != null) {
+ destroyAfterPendingTransaction(mSurfaceControl);
+ mSurfaceControl = null;
+ }
+
if (mParent != null) {
mParent.removeChild(this);
}
@@ -195,6 +226,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (mController != null) {
setController(null);
}
+
}
/**
@@ -286,11 +318,24 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* @see #mFullConfiguration
*/
@Override
- final public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ // We must diff before the configuration is applied so that we can capture the change
+ // against the existing bounds.
+ final int diff = diffOverrideBounds(overrideConfiguration.windowConfiguration.getBounds());
super.onOverrideConfigurationChanged(overrideConfiguration);
if (mParent != null) {
mParent.onDescendantOverrideConfigurationChanged();
}
+
+ if (diff == BOUNDS_CHANGE_NONE) {
+ return;
+ }
+
+ if ((diff & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
+ onResize();
+ } else {
+ onMovedByResize();
+ }
}
/**
@@ -407,7 +452,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/**
-a * Returns whether this child is on top of the window hierarchy.
+ * @return Whether this child is on top of the window hierarchy.
*/
boolean isOnTop() {
return getParent().getTopChild() == this && getParent().isOnTop();
@@ -673,6 +718,82 @@ a * Returns whether this child is on top of the window hierarchy.
mController = controller;
}
+ SurfaceControl.Builder makeSurface() {
+ final WindowContainer p = getParent();
+ return p.makeChildSurface(this);
+ }
+
+ /**
+ * @param child The WindowContainer this child surface is for, or null if the Surface
+ * is not assosciated with a WindowContainer (e.g. a surface used for Dimming).
+ */
+ SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+ final WindowContainer p = getParent();
+ // Give the parent a chance to set properties. In hierarchy v1 we rely
+ // on this to set full-screen dimensions on all our Surface-less Layers.
+ return p.makeChildSurface(child)
+ .setParent(mSurfaceControl);
+ }
+
+ /**
+ * @return Whether this WindowContainer should be magnified by the accessibility magnifier.
+ */
+ boolean shouldMagnify() {
+ for (int i = 0; i < mChildren.size(); i++) {
+ if (!mChildren.get(i).shouldMagnify()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ SurfaceSession getSession() {
+ if (getParent() != null) {
+ return getParent().getSession();
+ }
+ return null;
+ }
+
+ void assignLayer(Transaction t, int layer) {
+ if (mSurfaceControl != null) {
+ t.setLayer(mSurfaceControl, layer);
+ }
+ }
+
+ void assignChildLayers(Transaction t) {
+ int layer = 0;
+ boolean boosting = false;
+
+ // We use two passes as a way to promote children which
+ // need Z-boosting to the end of the list.
+ for (int i = 0; i < 2; i++ ) {
+ for (int j = 0; j < mChildren.size(); ++j) {
+ final WindowContainer wc = mChildren.get(j);
+ if (wc.needsZBoost() && !boosting) {
+ continue;
+ }
+ wc.assignLayer(t, layer);
+ wc.assignChildLayers(t);
+
+ layer++;
+ }
+ boosting = true;
+ }
+ }
+
+ void assignChildLayers() {
+ assignChildLayers(getPendingTransaction());
+ }
+
+ boolean needsZBoost() {
+ for (int i = 0; i < mChildren.size(); i++) {
+ if (mChildren.get(i).needsZBoost()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Write to a protocol buffer output stream. Protocol buffer message definition is at
* {@link com.android.server.wm.proto.WindowContainerProto}.
@@ -719,4 +840,59 @@ a * Returns whether this child is on top of the window hierarchy.
mConsumerWrapperPool.release(this);
}
}
+
+ // TODO(b/68336570): Should this really be on WindowContainer since it
+ // can only be used on the top-level nodes that aren't animated?
+ // (otherwise we would be fighting other callers of setMatrix).
+ void applyMagnificationSpec(Transaction t, MagnificationSpec spec) {
+ if (shouldMagnify()) {
+ t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
+ .setPosition(mSurfaceControl, spec.offsetX, spec.offsetY);
+ } else {
+ for (int i = 0; i < mChildren.size(); i++) {
+ mChildren.get(i).applyMagnificationSpec(t, spec);
+ }
+ }
+ }
+
+ /**
+ * TODO: Once we totally eliminate global transaction we will pass transaction in here
+ * rather than merging to global.
+ */
+ void prepareSurfaces() {
+ SurfaceControl.mergeToGlobalTransaction(getPendingTransaction());
+ for (int i = 0; i < mChildren.size(); i++) {
+ mChildren.get(i).prepareSurfaces();
+ }
+ }
+
+ /**
+ * Trigger a call to prepareSurfaces from the animation thread, such that
+ * mPendingTransaction will be applied.
+ */
+ void scheduleAnimation() {
+ if (mParent != null) {
+ mParent.scheduleAnimation();
+ }
+ }
+
+ SurfaceControl getSurfaceControl() {
+ return mSurfaceControl;
+ }
+
+ /**
+ * Destroy a given surface after executing mPendingTransaction. This is
+ * largely a workaround for destroy not being part of transactions
+ * rather than an intentional design, so please take care when
+ * expanding use.
+ */
+ void destroyAfterPendingTransaction(SurfaceControl surface) {
+ if (mParent != null) {
+ mParent.destroyAfterPendingTransaction(surface);
+ }
+ }
+
+ Transaction getPendingTransaction() {
+ return mPendingTransaction;
+ }
}
diff --git a/com/android/server/wm/WindowLayersController.java b/com/android/server/wm/WindowLayersController.java
deleted file mode 100644
index 7caf2fe9..00000000
--- a/com/android/server/wm/WindowLayersController.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.util.Slog;
-
-import java.util.ArrayDeque;
-import java.util.function.Consumer;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
-import static com.android.server.wm.WindowManagerService.WINDOW_LAYER_MULTIPLIER;
-
-/**
- * Controller for assigning layers to windows on the display.
- *
- * This class encapsulates general algorithm for assigning layers and special rules that we need to
- * apply on top. The general algorithm goes through windows from bottom to the top and the higher
- * the window is, the higher layer is assigned. The final layer is equal to base layer +
- * adjustment from the order. This means that the window list is assumed to be ordered roughly by
- * the base layer (there are exceptions, e.g. due to keyguard and wallpaper and they need to be
- * handled with care, because they break the algorithm).
- *
- * On top of the general algorithm we add special rules, that govern such amazing things as:
- * <li>IME (which has higher base layer, but will be positioned above application windows)</li>
- * <li>docked/pinned windows (that need to be lifted above other application windows, including
- * animations)
- * <li>dock divider (which needs to live above applications, but below IME)</li>
- * <li>replaced windows, which need to live above their normal level, because they anticipate
- * an animation</li>.
- */
-class WindowLayersController {
- private final WindowManagerService mService;
-
- WindowLayersController(WindowManagerService service) {
- mService = service;
- }
-
- private ArrayDeque<WindowState> mPinnedWindows = new ArrayDeque<>();
- private ArrayDeque<WindowState> mDockedWindows = new ArrayDeque<>();
- private ArrayDeque<WindowState> mAssistantWindows = new ArrayDeque<>();
- private ArrayDeque<WindowState> mInputMethodWindows = new ArrayDeque<>();
- private WindowState mDockDivider = null;
- private ArrayDeque<WindowState> mReplacingWindows = new ArrayDeque<>();
- private int mCurBaseLayer;
- private int mCurLayer;
- private boolean mAnyLayerChanged;
- private int mHighestApplicationLayer;
- private int mHighestDockedAffectedLayer;
- private int mHighestLayerInImeTargetBaseLayer;
- private WindowState mImeTarget;
- private boolean mAboveImeTarget;
- private ArrayDeque<WindowState> mAboveImeTargetAppWindows = new ArrayDeque();
-
- private final Consumer<WindowState> mAssignWindowLayersConsumer = w -> {
- boolean layerChanged = false;
-
- int oldLayer = w.mLayer;
- if (w.mBaseLayer == mCurBaseLayer) {
- mCurLayer += WINDOW_LAYER_MULTIPLIER;
- } else {
- mCurBaseLayer = mCurLayer = w.mBaseLayer;
- }
- assignAnimLayer(w, mCurLayer);
-
- // TODO: Preserved old behavior of code here but not sure comparing oldLayer to
- // mAnimLayer and mLayer makes sense...though the worst case would be unintentional
- // layer reassignment.
- if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
- layerChanged = true;
- mAnyLayerChanged = true;
- }
-
- if (w.mAppToken != null) {
- mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
- w.mWinAnimator.mAnimLayer);
- }
- if (mImeTarget != null && w.mBaseLayer == mImeTarget.mBaseLayer) {
- mHighestLayerInImeTargetBaseLayer = Math.max(mHighestLayerInImeTargetBaseLayer,
- w.mWinAnimator.mAnimLayer);
- }
- if (w.getAppToken() != null && w.inSplitScreenSecondaryWindowingMode()) {
- mHighestDockedAffectedLayer = Math.max(mHighestDockedAffectedLayer,
- w.mWinAnimator.mAnimLayer);
- }
-
- collectSpecialWindows(w);
-
- if (layerChanged) {
- w.scheduleAnimationIfDimming();
- }
- };
-
- final void assignWindowLayers(DisplayContent dc) {
- if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based",
- new RuntimeException("here").fillInStackTrace());
-
- reset();
- dc.forAllWindows(mAssignWindowLayersConsumer, false /* traverseTopToBottom */);
-
- adjustSpecialWindows();
-
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null && mAnyLayerChanged
- && dc.getDisplayId() == DEFAULT_DISPLAY) {
- mService.mAccessibilityController.onWindowLayersChangedLocked();
- }
-
- if (DEBUG_LAYERS) logDebugLayers(dc);
- }
-
- private void logDebugLayers(DisplayContent dc) {
- dc.forAllWindows((w) -> {
- final WindowStateAnimator winAnimator = w.mWinAnimator;
- Slog.v(TAG_WM, "Assign layer " + w + ": " + "mBase=" + w.mBaseLayer
- + " mLayer=" + w.mLayer + (w.mAppToken == null
- ? "" : " mAppLayer=" + w.mAppToken.getAnimLayerAdjustment())
- + " =mAnimLayer=" + winAnimator.mAnimLayer);
- }, false /* traverseTopToBottom */);
- }
-
- private void reset() {
- mPinnedWindows.clear();
- mInputMethodWindows.clear();
- mDockedWindows.clear();
- mAssistantWindows.clear();
- mReplacingWindows.clear();
- mDockDivider = null;
-
- mCurBaseLayer = 0;
- mCurLayer = 0;
- mAnyLayerChanged = false;
-
- mHighestApplicationLayer = 0;
- mHighestDockedAffectedLayer = 0;
- mHighestLayerInImeTargetBaseLayer = (mImeTarget != null) ? mImeTarget.mBaseLayer : 0;
- mImeTarget = mService.mInputMethodTarget;
- mAboveImeTarget = false;
- mAboveImeTargetAppWindows.clear();
- }
-
- private void collectSpecialWindows(WindowState w) {
- if (w.mAttrs.type == TYPE_DOCK_DIVIDER) {
- mDockDivider = w;
- return;
- }
- if (w.mWillReplaceWindow) {
- mReplacingWindows.add(w);
- }
- if (w.mIsImWindow) {
- mInputMethodWindows.add(w);
- return;
- }
- if (mImeTarget != null) {
- if (w.getParentWindow() == mImeTarget && w.mSubLayer > 0) {
- // Child windows of the ime target with a positive sub-layer should be placed above
- // the IME.
- mAboveImeTargetAppWindows.add(w);
- } else if (mAboveImeTarget && w.mAppToken != null) {
- // windows of apps above the IME target should be placed above the IME.
- mAboveImeTargetAppWindows.add(w);
- }
- if (w == mImeTarget) {
- mAboveImeTarget = true;
- }
- }
-
- final int windowingMode = w.getWindowingMode();
- if (windowingMode == WINDOWING_MODE_PINNED) {
- mPinnedWindows.add(w);
- } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- mDockedWindows.add(w);
- }
- if (w.isActivityTypeAssistant()) {
- mAssistantWindows.add(w);
- }
- }
-
- private void adjustSpecialWindows() {
- // The following adjustments are beyond the highest docked-affected layer
- int layer = mHighestDockedAffectedLayer + TYPE_LAYER_OFFSET;
-
- // Adjust the docked stack windows and dock divider above only the windows that are affected
- // by the docked stack. When this happens, also boost the assistant window layers, otherwise
- // the docked stack windows & divider would be promoted above the assistant.
- if (!mDockedWindows.isEmpty() && mHighestDockedAffectedLayer > 0) {
- while (!mDockedWindows.isEmpty()) {
- final WindowState window = mDockedWindows.remove();
- layer = assignAndIncreaseLayerIfNeeded(window, layer);
- }
-
- layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer);
-
- while (!mAssistantWindows.isEmpty()) {
- final WindowState window = mAssistantWindows.remove();
- if (window.mLayer > mHighestDockedAffectedLayer) {
- layer = assignAndIncreaseLayerIfNeeded(window, layer);
- }
- }
- }
-
- // The following adjustments are beyond the highest app layer or boosted layer
- layer = Math.max(layer, mHighestApplicationLayer + WINDOW_LAYER_MULTIPLIER);
-
- // We know that we will be animating a relaunching window in the near future, which will
- // receive a z-order increase. We want the replaced window to immediately receive the same
- // treatment, e.g. to be above the dock divider.
- while (!mReplacingWindows.isEmpty()) {
- layer = assignAndIncreaseLayerIfNeeded(mReplacingWindows.remove(), layer);
- }
-
- while (!mPinnedWindows.isEmpty()) {
- layer = assignAndIncreaseLayerIfNeeded(mPinnedWindows.remove(), layer);
- }
-
- // Make sure IME is the highest window in the base layer of it's target.
- if (mImeTarget != null) {
- if (mImeTarget.mAppToken == null) {
- // For non-app ime targets adjust the layer we start from to match what we found
- // when assigning layers. Otherwise, just use the highest app layer we have some far.
- layer = mHighestLayerInImeTargetBaseLayer + WINDOW_LAYER_MULTIPLIER;
- }
-
- while (!mInputMethodWindows.isEmpty()) {
- layer = assignAndIncreaseLayerIfNeeded(mInputMethodWindows.remove(), layer);
- }
-
- // Adjust app windows the should be displayed above the IME since they are above the IME
- // target.
- while (!mAboveImeTargetAppWindows.isEmpty()) {
- layer = assignAndIncreaseLayerIfNeeded(mAboveImeTargetAppWindows.remove(), layer);
- }
- }
-
- }
-
- private int assignAndIncreaseLayerIfNeeded(WindowState win, int layer) {
- if (win != null) {
- assignAnimLayer(win, layer);
- // Make sure we leave space in-between normal windows for dims and such.
- layer += WINDOW_LAYER_MULTIPLIER;
- }
- return layer;
- }
-
- private void assignAnimLayer(WindowState w, int layer) {
- w.mLayer = layer;
- w.mWinAnimator.mAnimLayer = w.getAnimLayerAdjustment()
- + w.getSpecialWindowAnimLayerAdjustment();
- if (w.mAppToken != null) {
- w.mAppToken.mAppAnimator.updateThumbnailLayer();
- }
- }
-}
diff --git a/android/view/WindowManagerInternal.java b/com/android/server/wm/WindowManagerInternal.java
index cd1b1908..036f7b0a 100644
--- a/android/view/WindowManagerInternal.java
+++ b/com/android/server/wm/WindowManagerInternal.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,7 +14,7 @@
* limitations under the License.
*/
-package android.view;
+package com.android.server.wm;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -23,8 +23,14 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
import android.os.IBinder;
+import android.view.IInputFilter;
+import android.view.IWindow;
+import android.view.MagnificationSpec;
+import android.view.WindowInfo;
import android.view.animation.Animation;
+import com.android.server.policy.WindowManagerPolicy;
+
import java.util.List;
/**
@@ -381,4 +387,9 @@ public abstract class WindowManagerInternal {
* Sets callback to DragDropController.
*/
public abstract void registerDragDropControllerCallback(IDragDropCallback callback);
+
+ /**
+ * @see android.view.IWindowManager#lockNow
+ */
+ public abstract void lockNow();
}
diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java
index ce343061..82f842c0 100644
--- a/com/android/server/wm/WindowManagerService.java
+++ b/com/android/server/wm/WindowManagerService.java
@@ -65,10 +65,10 @@ import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.LockGuard.INDEX_WINDOW;
import static com.android.server.LockGuard.installLock;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
@@ -126,7 +126,6 @@ import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IAssistDataReceiver;
import android.content.BroadcastReceiver;
-import android.content.ClipData;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -219,10 +218,7 @@ import android.view.WindowContentFrameStats;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
-import android.view.WindowManagerInternal;
-import android.view.WindowManagerPolicy;
-import android.view.WindowManagerPolicy.PointerEventListener;
-import android.view.WindowManagerPolicy.ScreenOffListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.view.animation.Animation;
import android.view.inputmethod.InputMethodManagerInternal;
@@ -245,7 +241,8 @@ import com.android.server.LocalServices;
import com.android.server.UiThread;
import com.android.server.Watchdog;
import com.android.server.input.InputManagerService;
-import android.view.DisplayFrames;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
@@ -529,7 +526,6 @@ public class WindowManagerService extends IWindowManager.Stub
AccessibilityController mAccessibilityController;
- final SurfaceSession mFxSession;
Watermark mWatermark;
StrictModeFlash mStrictModeFlash;
CircularDisplayMask mCircularDisplayMask;
@@ -804,6 +800,13 @@ public class WindowManagerService extends IWindowManager.Stub
static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
new WindowManagerThreadPriorityBooster();
+ class DefaultSurfaceBuilderFactory implements SurfaceBuilderFactory {
+ public SurfaceControl.Builder make(SurfaceSession s) {
+ return new SurfaceControl.Builder(s);
+ }
+ };
+ SurfaceBuilderFactory mSurfaceBuilderFactory = new DefaultSurfaceBuilderFactory();
+
static void boostPriorityForLockedSection() {
sThreadPriorityBooster.boost();
}
@@ -992,7 +995,6 @@ public class WindowManagerService extends IWindowManager.Stub
mPointerEventDispatcher = null;
}
- mFxSession = new SurfaceSession();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
mDisplays = mDisplayManager.getDisplays();
for (Display display : mDisplays) {
@@ -2478,11 +2480,8 @@ public class WindowManagerService extends IWindowManager.Stub
mWaitingForConfig = true;
displayContent.setLayoutNeeded();
int anim[] = new int[2];
- if (displayContent.isDimming()) {
- anim[0] = anim[1] = 0;
- } else {
- mPolicy.selectRotationAnimationLw(anim);
- }
+ mPolicy.selectRotationAnimationLw(anim);
+
startFreezingDisplayLocked(false, anim[0], anim[1], displayContent);
config = new Configuration(mTempConfiguration);
}
@@ -3592,8 +3591,7 @@ public class WindowManagerService extends IWindowManager.Stub
com.android.internal.R.dimen.circular_display_mask_thickness);
mCircularDisplayMask = new CircularDisplayMask(
- getDefaultDisplayContentLocked().getDisplay(),
- mFxSession,
+ getDefaultDisplayContentLocked(),
mPolicy.getWindowLayerFromTypeLw(
WindowManager.LayoutParams.TYPE_POINTER)
* TYPE_LAYER_MULTIPLIER + 10, screenOffset, maskThickness);
@@ -3621,8 +3619,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (mEmulatorDisplayOverlay == null) {
mEmulatorDisplayOverlay = new EmulatorDisplayOverlay(
mContext,
- getDefaultDisplayContentLocked().getDisplay(),
- mFxSession,
+ getDefaultDisplayContentLocked(),
mPolicy.getWindowLayerFromTypeLw(
WindowManager.LayoutParams.TYPE_POINTER)
* TYPE_LAYER_MULTIPLIER + 10);
@@ -3670,7 +3667,7 @@ public class WindowManagerService extends IWindowManager.Stub
// TODO(multi-display): support multiple displays
if (mStrictModeFlash == null) {
mStrictModeFlash = new StrictModeFlash(
- getDefaultDisplayContentLocked().getDisplay(), mFxSession);
+ getDefaultDisplayContentLocked());
}
mStrictModeFlash.setVisibility(on);
} finally {
@@ -3694,9 +3691,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper");
- return screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */,
- -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */,
- Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */);
+ return screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888,
+ true /* wallpaperOnly */);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -3715,10 +3711,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
FgThread.getHandler().post(() -> {
- Bitmap bm = screenshotApplications(null /* appToken */, DEFAULT_DISPLAY,
- -1 /* width */, -1 /* height */, true /* includeFullDisplay */,
- 1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
- false /* includeDecor */);
+ Bitmap bm = screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888,
+ false /* wallpaperOnly */);
try {
receiver.onHandleAssistScreenshot(bm);
} catch (RemoteException e) {
@@ -3752,29 +3746,21 @@ public class WindowManagerService extends IWindowManager.Stub
* In portrait mode, it grabs the full screenshot.
*
* @param displayId the Display to take a screenshot of.
- * @param width the width of the target bitmap
- * @param height the height of the target bitmap
- * @param includeFullDisplay true if the screen should not be cropped before capture
- * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
* @param config of the output bitmap
* @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
- * @param includeDecor whether to include window decors, like the status or navigation bar
- * background of the window
*/
- private Bitmap screenshotApplications(IBinder appToken, int displayId, int width,
- int height, boolean includeFullDisplay, float frameScale, Bitmap.Config config,
- boolean wallpaperOnly, boolean includeDecor) {
+ private Bitmap screenshotApplications(int displayId, Bitmap.Config config,
+ boolean wallpaperOnly) {
final DisplayContent displayContent;
synchronized(mWindowMap) {
displayContent = mRoot.getDisplayContentOrCreate(displayId);
if (displayContent == null) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
- + ": returning null. No Display for displayId=" + displayId);
+ if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot returning null. No Display for "
+ + "displayId=" + displayId);
return null;
}
}
- return displayContent.screenshotApplications(appToken, width, height,
- includeFullDisplay, frameScale, config, wallpaperOnly, includeDecor);
+ return displayContent.screenshotDisplay(config, wallpaperOnly);
}
/**
@@ -4522,7 +4508,7 @@ public class WindowManagerService extends IWindowManager.Stub
Display display = displayContent.getDisplay();
mTaskPositioner = new TaskPositioner(this);
- mTaskPositioner.register(display);
+ mTaskPositioner.register(displayContent);
mInputMonitor.updateInputWindowsLw(true /*force*/);
// We need to grab the touch focus so that the touch events during the
@@ -5519,7 +5505,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
void reconfigureDisplayLocked(@NonNull DisplayContent displayContent) {
- if (!mDisplayReady) {
+ if (!displayContent.isReady()) {
return;
}
displayContent.configureDisplayPolicy();
@@ -5892,7 +5878,7 @@ public class WindowManagerService extends IWindowManager.Stub
displayContent.updateDisplayInfo();
screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
- mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure,
+ inTransaction, mPolicy.isDefaultOrientationForced(), isSecure,
this);
mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId,
screenRotationAnimation);
@@ -5952,11 +5938,10 @@ public class WindowManagerService extends IWindowManager.Stub
// TODO(multidisplay): rotation on main screen only.
DisplayInfo displayInfo = displayContent.getDisplayInfo();
// Get rotation animation again, with new top window
- boolean isDimming = displayContent.isDimming();
- if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, isDimming)) {
+ if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, false)) {
mExitAnimId = mEnterAnimId = 0;
}
- if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
+ if (screenRotationAnimation.dismiss(MAX_ANIMATION_DURATION,
getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
scheduleAnimationLocked();
@@ -6040,8 +6025,8 @@ public class WindowManagerService extends IWindowManager.Stub
if (toks != null && toks.length > 0) {
// TODO(multi-display): Show watermarks on secondary displays.
final DisplayContent displayContent = getDefaultDisplayContentLocked();
- mWatermark = new Watermark(displayContent.getDisplay(),
- displayContent.mRealDisplayMetrics, mFxSession, toks);
+ mWatermark = new Watermark(displayContent, displayContent.mRealDisplayMetrics,
+ toks);
}
}
} catch (FileNotFoundException e) {
@@ -7456,6 +7441,11 @@ public class WindowManagerService extends IWindowManager.Stub
public void registerDragDropControllerCallback(IDragDropCallback callback) {
mDragDropController.registerCallback(callback);
}
+
+ @Override
+ public void lockNow() {
+ WindowManagerService.this.lockNow(null);
+ }
}
void registerAppFreezeListener(AppFreezeListener listener) {
@@ -7557,4 +7547,13 @@ public class WindowManagerService extends IWindowManager.Stub
w.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
}, false /* traverseTopToBottom */);
}
+
+ public void applyMagnificationSpec(MagnificationSpec spec) {
+ getDefaultDisplayContentLocked().applyMagnificationSpec(spec);
+ }
+
+ SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
+ return mSurfaceBuilderFactory.make(s);
+ }
}
+
diff --git a/com/android/server/wm/WindowManagerShellCommand.java b/com/android/server/wm/WindowManagerShellCommand.java
index 4b98d9d9..23586955 100644
--- a/com/android/server/wm/WindowManagerShellCommand.java
+++ b/com/android/server/wm/WindowManagerShellCommand.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.os.Build.IS_USER;
+
import android.os.ShellCommand;
import java.io.PrintWriter;
@@ -50,8 +52,10 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println(" help");
pw.println(" Print this help text.");
pw.println();
- pw.println(" tracing (start | stop)");
- pw.println(" start or stop window tracing");
- pw.println();
+ if (!IS_USER){
+ pw.println(" tracing (start | stop)");
+ pw.println(" start or stop window tracing");
+ pw.println();
+ }
}
}
diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java
index 6b1932d7..c2ac9052 100644
--- a/com/android/server/wm/WindowState.java
+++ b/com/android/server/wm/WindowState.java
@@ -23,6 +23,7 @@ import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+import static android.view.SurfaceControl.Transaction;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
@@ -41,6 +42,7 @@ import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
@@ -54,6 +56,10 @@ import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
@@ -61,10 +67,10 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED
import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static android.view.WindowManagerPolicy.TRANSIT_ENTER;
-import static android.view.WindowManagerPolicy.TRANSIT_EXIT;
-import static android.view.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
@@ -147,14 +153,16 @@ import android.view.IWindowId;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowInfo;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.input.InputWindowHandle;
+import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -602,6 +610,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
};
};
+ /**
+ * Indicates whether we have requested a Dim (in the sense of {@link Dimmer}) from our host
+ * container.
+ */
+ private boolean mIsDimming = false;
+
+ private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
+
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
@@ -799,7 +815,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
layoutXDiff = 0;
layoutYDiff = 0;
} else {
- getContainerBounds(mContainingFrame);
+ getBounds(mContainingFrame);
if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
// If the bounds are frozen, we still want to translate the window freely and only
@@ -957,7 +973,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mContentInsets.setEmpty();
mVisibleInsets.setEmpty();
} else {
- getDisplayContent().getLogicalDisplayRect(mTmpRect);
+ getDisplayContent().getBounds(mTmpRect);
// Override right and/or bottom insets in case if the frame doesn't fit the screen in
// non-fullscreen mode.
boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer
@@ -1029,6 +1045,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
+ " of=" + mOutsets.toShortString());
}
+ // TODO: Look into whether this override is still necessary.
+ @Override
+ public Rect getBounds() {
+ if (isInMultiWindowMode()) {
+ return getTask().getBounds();
+ } else if (mAppToken != null){
+ return mAppToken.getBounds();
+ } else {
+ return super.getBounds();
+ }
+ }
+
@Override
public Rect getFrameLw() {
return mFrame;
@@ -2034,23 +2062,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return isVisibleOrAdding();
}
- void scheduleAnimationIfDimming() {
- final DisplayContent dc = getDisplayContent();
- if (dc == null) {
- return;
- }
-
- // If layout is currently deferred, we want to hold of with updating the layers.
- if (mService.mWindowPlacerLocked.isLayoutDeferred()) {
- return;
- }
- final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
- if (dimLayerUser != null && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator)) {
- // Force an animation pass just to update the mDimLayer layer.
- mService.scheduleAnimationLocked();
- }
- }
-
private final class DeadWindowEventReceiver extends InputEventReceiver {
DeadWindowEventReceiver(InputChannel inputChannel) {
super(inputChannel, mService.mH.getLooper());
@@ -2106,31 +2117,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mInputWindowHandle.inputChannel = null;
}
- void applyDimLayerIfNeeded() {
- // When the app is terminated (eg. from Recents), the task might have already been
- // removed with the window pending removal. Don't apply dim in such cases, as there
- // will be no more updateDimLayer() calls, which leaves the dimlayer invalid.
- final AppWindowToken token = mAppToken;
- if (token != null && token.removed) {
- return;
- }
-
- final DisplayContent dc = getDisplayContent();
- if (!mAnimatingExit && mAppDied) {
- // If app died visible, apply a dim over the window to indicate that it's inactive
- dc.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
- } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
- && dc != null && !mAnimatingExit && isVisible()) {
- dc.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator);
- }
- }
-
- private DimLayer.DimLayerUser getDimLayerUser() {
+ private Dimmer getDimmer() {
Task task = getTask();
if (task != null) {
- return task;
+ return task.getDimmer();
+ }
+ TaskStack taskStack = getStack();
+ if (taskStack != null) {
+ return taskStack.getDimmer();
}
- return getStack();
+ return null;
}
/** Returns true if the replacement window was removed. */
@@ -2152,9 +2148,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
private void removeReplacedWindow() {
if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + this);
- if (isDimming()) {
- transferDimToReplacement();
- }
mWillReplaceWindow = false;
mAnimateReplacingWindow = false;
mReplacingRemoveRequested = false;
@@ -2217,11 +2210,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// need to intercept touches outside of that window. The dim layer user
// associated with the window (task or stack) will give us the good bounds, as
// they would be used to display the dim layer.
- final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
- if (dimLayerUser != null) {
- dimLayerUser.getDimBounds(mTmpRect);
+ final Task task = getTask();
+ if (task != null) {
+ task.getDimBounds(mTmpRect);
} else {
- getVisibleBounds(mTmpRect);
+ getStack().getDimBounds(mTmpRect);
}
if (inFreeformWindowingMode()) {
// For freeform windows we the touch region to include the whole surface for the
@@ -2465,7 +2458,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mPolicyVisibility = true;
mPolicyVisibilityAfterAnim = true;
if (doAnimation) {
- mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_ENTER, true);
+ mWinAnimator.applyAnimationLocked(TRANSIT_ENTER, true);
}
if (requestAnim) {
mService.scheduleAnimationLocked();
@@ -2493,7 +2486,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return false;
}
if (doAnimation) {
- mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, false);
+ mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
if (mWinAnimator.mAnimation == null) {
doAnimation = false;
}
@@ -2729,14 +2722,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return displayContent.isDefaultDisplay;
}
- @Override
- public boolean isDimming() {
- final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
- final DisplayContent dc = getDisplayContent();
- return dimLayerUser != null && dc != null
- && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator);
- }
-
void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
mShowToOwnerOnly = showToOwnerOnly;
}
@@ -2979,33 +2964,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
/** Is this window in a container that takes up the entire screen space? */
private boolean inFullscreenContainer() {
- if (mAppToken == null) {
- return true;
- }
- if (mAppToken.hasBounds()) {
- return false;
- }
- return !isInMultiWindowMode();
+ return mAppToken == null || (mAppToken.matchParentBounds() && !isInMultiWindowMode());
}
/** @return true when the window is in fullscreen task, but has non-fullscreen bounds set. */
boolean isLetterboxedAppWindow() {
- final Task task = getTask();
- final boolean taskIsFullscreen = task != null && task.isFullscreen();
- final boolean appWindowIsFullscreen = mAppToken != null && !mAppToken.hasBounds();
-
- return taskIsFullscreen && !appWindowIsFullscreen;
- }
-
- /** Returns the appropriate bounds to use for computing frames. */
- private void getContainerBounds(Rect outBounds) {
- if (isInMultiWindowMode()) {
- getTask().getBounds(outBounds);
- } else if (mAppToken != null){
- mAppToken.getBounds(outBounds);
- } else {
- outBounds.setEmpty();
- }
+ return !isInMultiWindowMode() && mAppToken != null && !mAppToken.matchParentBounds();
}
boolean isDragResizeChanged() {
@@ -3071,7 +3035,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (task == null) {
return false;
}
- if (!inSplitScreenWindowingMode()) {
+ if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()) {
return false;
}
if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
@@ -3591,15 +3555,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return winY;
}
- private void transferDimToReplacement() {
- final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
- final DisplayContent dc = getDisplayContent();
- if (dimLayerUser != null && dc != null) {
- dc.mDimLayerController.applyDim(dimLayerUser,
- mReplacementWindow.mWinAnimator, (mAttrs.flags & FLAG_DIM_BEHIND) != 0);
- }
- }
-
// During activity relaunch due to resize, we sometimes use window replacement
// for only child windows (as the main window is handled by window preservation)
// and the big surface.
@@ -4156,9 +4111,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
policyCrop.intersect(-mCompatFrame.left, -mCompatFrame.top,
displayInfo.logicalWidth - mCompatFrame.left,
displayInfo.logicalHeight - mCompatFrame.top);
- } else if (mLayer >= mService.mSystemDecorLayer) {
- // Above the decor layer is easy, just use the entire window
- policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height());
} else if (mDecorFrame.isEmpty()) {
// Windows without policy decor aren't cropped.
policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height());
@@ -4388,4 +4340,78 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return false;
}
}
+
+ @Override
+ boolean shouldMagnify() {
+ if (mAttrs.type == TYPE_INPUT_METHOD ||
+ mAttrs.type == TYPE_INPUT_METHOD_DIALOG ||
+ mAttrs.type == TYPE_MAGNIFICATION_OVERLAY ||
+ mAttrs.type == TYPE_NAVIGATION_BAR ||
+ // It's tempting to wonder: Have we forgotten the rounded corners overlay?
+ // worry not: it's a fake TYPE_NAVIGATION_BAR_PANEL
+ mAttrs.type == TYPE_NAVIGATION_BAR_PANEL ||
+ mAttrs.type == TYPE_STATUS_BAR) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ SurfaceSession getSession() {
+ if (mSession.mSurfaceSession != null) {
+ return mSession.mSurfaceSession;
+ } else {
+ return getParent().getSession();
+ }
+ }
+
+ @Override
+ boolean needsZBoost() {
+ return getAnimLayerAdjustment() > 0 || mWillReplaceWindow;
+ }
+
+ @Override
+ SurfaceControl.Builder makeSurface() {
+ return getParent().makeChildSurface(this);
+ }
+
+ private void applyDims(Dimmer dimmer) {
+ if (!mAnimatingExit && mAppDied) {
+ mIsDimming = true;
+ getDimmer().dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
+ } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
+ && !mAnimatingExit && isVisible()) {
+ mIsDimming = true;
+ getDimmer().dimBelow(getPendingTransaction(), this, mAttrs.dimAmount);
+ }
+ }
+
+ @Override
+ void prepareSurfaces() {
+ final Dimmer dimmer = getDimmer();
+ mIsDimming = false;
+ if (dimmer != null) {
+ applyDims(dimmer);
+ }
+
+ mWinAnimator.prepareSurfaceLocked(true);
+ super.prepareSurfaces();
+ }
+
+ @Override
+ void assignLayer(Transaction t, int layer) {
+ // See comment in assignRelativeLayerForImeTargetChild
+ if (!isChildWindow()
+ || (mService.mInputMethodTarget != getParentWindow())
+ || !inSplitScreenWindowingMode()) {
+ super.assignLayer(t, layer);
+ return;
+ }
+ getDisplayContent().assignRelativeLayerForImeTargetChild(t, this);
+ }
+
+ @Override
+ public boolean isDimming() {
+ return mIsDimming;
+ }
}
diff --git a/com/android/server/wm/WindowStateAnimator.java b/com/android/server/wm/WindowStateAnimator.java
index 86397aea..4b5661f5 100644
--- a/com/android/server/wm/WindowStateAnimator.java
+++ b/com/android/server/wm/WindowStateAnimator.java
@@ -60,17 +60,17 @@ import android.os.Trace;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
-import android.view.MagnificationSpec;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerPolicy;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.Transformation;
+import com.android.server.policy.WindowManagerPolicy;
+
import java.io.PrintWriter;
import java.io.FileDescriptor;
@@ -559,7 +559,10 @@ class WindowStateAnimator {
}
if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin, "SET FREEZE LAYER", false);
if (mSurfaceController != null) {
- mSurfaceController.setLayer(mAnimLayer + 1);
+ // Our SurfaceControl is always at layer 0 within the parent Surface managed by
+ // window-state. We want this old Surface to stay on top of the new one
+ // until we do the swap, so we place it at layer 1.
+ mSurfaceController.mSurfaceControl.setLayer(1);
}
mDestroyPreservedSurfaceUponRedraw = true;
mSurfaceDestroyDeferred = true;
@@ -730,7 +733,6 @@ class WindowStateAnimator {
try {
mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top, false);
mSurfaceController.setLayerStackInTransaction(getLayerStack());
- mSurfaceController.setLayer(mAnimLayer);
} finally {
mService.closeSurfaceTransaction("createSurfaceLocked");
}
@@ -867,29 +869,11 @@ class WindowStateAnimator {
mPendingDestroySurface = null;
}
- void applyMagnificationSpec(MagnificationSpec spec, Matrix transform) {
- final int surfaceInsetLeft = mWin.mAttrs.surfaceInsets.left;
- final int surfaceInsetTop = mWin.mAttrs.surfaceInsets.top;
-
- if (spec != null && !spec.isNop()) {
- float scale = spec.scale;
- transform.postScale(scale, scale);
- transform.postTranslate(spec.offsetX, spec.offsetY);
-
- // As we are scaling the whole surface, to keep the content
- // in the same position we will also have to scale the surfaceInsets.
- transform.postTranslate(-(surfaceInsetLeft*scale - surfaceInsetLeft),
- -(surfaceInsetTop*scale - surfaceInsetTop));
- }
- }
-
void computeShownFrameLocked() {
final boolean selfTransformation = mHasLocalTransformation;
- Transformation attachedTransformation =
- (mParentWinAnimator != null && mParentWinAnimator.mHasLocalTransformation)
- ? mParentWinAnimator.mTransformation : null;
Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation)
? mAppAnimator.transformation : null;
+ Transformation wallpaperTargetTransformation = null;
// Wallpapers are animated based on the "real" window they
// are currently targeting.
@@ -899,9 +883,9 @@ class WindowStateAnimator {
if (wallpaperAnimator.mHasLocalTransformation &&
wallpaperAnimator.mAnimation != null &&
!wallpaperAnimator.mAnimation.getDetachWallpaper()) {
- attachedTransformation = wallpaperAnimator.mTransformation;
- if (DEBUG_WALLPAPER && attachedTransformation != null) {
- Slog.v(TAG, "WP target attached xform: " + attachedTransformation);
+ wallpaperTargetTransformation = wallpaperAnimator.mTransformation;
+ if (DEBUG_WALLPAPER && wallpaperTargetTransformation != null) {
+ Slog.v(TAG, "WP target attached xform: " + wallpaperTargetTransformation);
}
}
final AppWindowAnimator wpAppAnimator = wallpaperTarget.mAppToken == null ?
@@ -923,7 +907,7 @@ class WindowStateAnimator {
screenRotationAnimation != null && screenRotationAnimation.isAnimating();
mHasClipRect = false;
- if (selfTransformation || attachedTransformation != null
+ if (selfTransformation || wallpaperTargetTransformation != null
|| appTransformation != null || screenAnimation) {
// cache often used attributes locally
final Rect frame = mWin.mFrame;
@@ -953,27 +937,31 @@ class WindowStateAnimator {
if (selfTransformation) {
tmpMatrix.postConcat(mTransformation.getMatrix());
}
- if (attachedTransformation != null) {
- tmpMatrix.postConcat(attachedTransformation.getMatrix());
+
+ if (wallpaperTargetTransformation != null) {
+ tmpMatrix.postConcat(wallpaperTargetTransformation.getMatrix());
}
if (appTransformation != null) {
tmpMatrix.postConcat(appTransformation.getMatrix());
}
+ int left = frame.left;
+ int top = frame.top;
+ if (mWin.isChildWindow()) {
+ WindowState parent = mWin.getParentWindow();
+ left -= parent.mFrame.left;
+ top -= parent.mFrame.top;
+ }
+
// The translation that applies the position of the window needs to be applied at the
// end in case that other translations include scaling. Otherwise the scaling will
// affect this translation. But it needs to be set before the screen rotation animation
// so the pivot point is at the center of the screen for all windows.
- tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
+ tmpMatrix.postTranslate(left + mWin.mXOffset, top + mWin.mYOffset);
if (screenAnimation) {
tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
}
- MagnificationSpec spec = getMagnificationSpec();
- if (spec != null) {
- applyMagnificationSpec(spec, tmpMatrix);
- }
-
// "convert" it into SurfaceFlinger's format
// (a 2x2 matrix + an offset)
// Here we must not transform the position of the surface
@@ -1004,8 +992,8 @@ class WindowStateAnimator {
if (selfTransformation) {
mShownAlpha *= mTransformation.getAlpha();
}
- if (attachedTransformation != null) {
- mShownAlpha *= attachedTransformation.getAlpha();
+ if (wallpaperTargetTransformation != null) {
+ mShownAlpha *= wallpaperTargetTransformation.getAlpha();
}
if (appTransformation != null) {
mShownAlpha *= appTransformation.getAlpha();
@@ -1036,8 +1024,8 @@ class WindowStateAnimator {
&& (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
+ " self=" + (selfTransformation ? mTransformation.getAlpha() : "null")
- + " attached=" + (attachedTransformation == null ?
- "null" : attachedTransformation.getAlpha())
+ + " attached=" + (wallpaperTargetTransformation == null ?
+ "null" : wallpaperTargetTransformation.getAlpha())
+ " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha())
+ " screen=" + (screenAnimation ?
screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
@@ -1057,49 +1045,16 @@ class WindowStateAnimator {
TAG, "computeShownFrameLocked: " + this +
" not attached, mAlpha=" + mAlpha);
- MagnificationSpec spec = getMagnificationSpec();
- if (spec != null) {
- final Rect frame = mWin.mFrame;
- final float tmpFloats[] = mService.mTmpFloats;
- final Matrix tmpMatrix = mWin.mTmpMatrix;
-
- tmpMatrix.setScale(mWin.mGlobalScale, mWin.mGlobalScale);
- tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
-
- applyMagnificationSpec(spec, tmpMatrix);
-
- tmpMatrix.getValues(tmpFloats);
-
- mHaveMatrix = true;
- mDsDx = tmpFloats[Matrix.MSCALE_X];
- mDtDx = tmpFloats[Matrix.MSKEW_Y];
- mDtDy = tmpFloats[Matrix.MSKEW_X];
- mDsDy = tmpFloats[Matrix.MSCALE_Y];
- float x = tmpFloats[Matrix.MTRANS_X];
- float y = tmpFloats[Matrix.MTRANS_Y];
- mWin.mShownPosition.set(Math.round(x), Math.round(y));
-
- mShownAlpha = mAlpha;
- } else {
- mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top);
- if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
- mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset);
- }
- mShownAlpha = mAlpha;
- mHaveMatrix = false;
- mDsDx = mWin.mGlobalScale;
- mDtDx = 0;
- mDtDy = 0;
- mDsDy = mWin.mGlobalScale;
+ mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top);
+ if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
+ mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset);
}
- }
-
- private MagnificationSpec getMagnificationSpec() {
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null && mWin.getDisplayId() == DEFAULT_DISPLAY) {
- return mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin);
- }
- return null;
+ mShownAlpha = mAlpha;
+ mHaveMatrix = false;
+ mDsDx = mWin.mGlobalScale;
+ mDtDx = 0;
+ mDtDy = 0;
+ mDsDy = mWin.mGlobalScale;
}
/**
@@ -1140,26 +1095,6 @@ class WindowStateAnimator {
w.expandForSurfaceInsets(finalClipRect);
}
- // We may be applying a magnification spec to all windows,
- // simulating a transformation in screen space, in which case
- // we need to transform all other screen space values...including
- // the final crop. This is kind of messed up and we should look
- // in to actually transforming screen-space via a parent-layer.
- // b/38322835
- MagnificationSpec spec = getMagnificationSpec();
- if (spec != null && !spec.isNop()) {
- Matrix transform = mWin.mTmpMatrix;
- RectF finalCrop = mService.mTmpRectF;
- transform.reset();
- transform.postScale(spec.scale, spec.scale);
- transform.postTranslate(-spec.offsetX, -spec.offsetY);
- transform.mapRect(finalCrop);
- finalClipRect.top = (int) finalCrop.top;
- finalClipRect.left = (int) finalCrop.left;
- finalClipRect.right = (int) finalCrop.right;
- finalClipRect.bottom = (int) finalCrop.bottom;
- }
-
return true;
}
@@ -1517,7 +1452,6 @@ class WindowStateAnimator {
mReportSurfaceResized = true;
mAnimator.setPendingLayoutChanges(w.getDisplayId(),
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
- w.applyDimLayerIfNeeded();
}
}
@@ -1615,7 +1549,6 @@ class WindowStateAnimator {
mDtDy * w.mHScale * mExtraHScale,
mDsDy * w.mVScale * mExtraVScale,
recoveringMemory);
- mSurfaceController.setLayer(mAnimLayer);
if (prepared && mDrawState == HAS_DRAWN) {
if (mLastHidden) {
@@ -2045,7 +1978,7 @@ class WindowStateAnimator {
final float width = w.mFrame.width();
final float height = w.mFrame.height();
- mService.getDefaultDisplayContentLocked().getLogicalDisplayRect(displayRect);
+ mService.getDefaultDisplayContentLocked().getBounds(displayRect);
final float displayWidth = displayRect.width();
final float displayHeight = displayRect.height();
diff --git a/com/android/server/wm/WindowSurfaceController.java b/com/android/server/wm/WindowSurfaceController.java
index a2145230..6746754b 100644
--- a/com/android/server/wm/WindowSurfaceController.java
+++ b/com/android/server/wm/WindowSurfaceController.java
@@ -53,7 +53,7 @@ class WindowSurfaceController {
final WindowStateAnimator mAnimator;
- private SurfaceControlWithBackground mSurfaceControl;
+ SurfaceControlWithBackground mSurfaceControl;
// Should only be set from within setShown().
private boolean mSurfaceShown = false;
@@ -101,7 +101,8 @@ class WindowSurfaceController {
mWindowSession = win.mSession;
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
- final SurfaceControl.Builder b = new SurfaceControl.Builder(s)
+ final SurfaceControl.Builder b = win.makeSurface()
+ .setParent(win.getSurfaceControl())
.setName(name)
.setSize(w, h)
.setFormat(format)
@@ -245,25 +246,6 @@ class WindowSurfaceController {
}
}
- void setLayer(int layer) {
- if (mSurfaceControl != null) {
- mService.openSurfaceTransaction();
- try {
- if (mAnimator.mWin.usesRelativeZOrdering()) {
- mSurfaceControl.setRelativeLayer(
- mAnimator.mWin.getParentWindow()
- .mWinAnimator.mSurfaceController.mSurfaceControl,
- -1);
- } else {
- mSurfaceLayer = layer;
- mSurfaceControl.setLayer(layer);
- }
- } finally {
- mService.closeSurfaceTransaction("setLayer");
- }
- }
- }
-
void setLayerStackInTransaction(int layerStack) {
if (mSurfaceControl != null) {
mSurfaceControl.setLayerStack(layerStack);
diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java
index cd5e4750..169d0a3a 100644
--- a/com/android/server/wm/WindowSurfacePlacer.java
+++ b/com/android/server/wm/WindowSurfacePlacer.java
@@ -20,8 +20,8 @@ import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManagerInternal.APP_TRANSITION_SNAPSHOT;
import static android.app.ActivityManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
import static android.app.ActivityManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
@@ -704,7 +704,7 @@ class WindowSurfacePlacer {
// Create a new surface for the thumbnail
WindowState window = appToken.findMainWindow();
- final SurfaceControl surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+ final SurfaceControl surfaceControl = appToken.makeSurface()
.setName("thumbnail anim")
.setSize(dirty.width(), dirty.height())
.setFormat(PixelFormat.TRANSLUCENT)
@@ -712,7 +712,6 @@ class WindowSurfacePlacer {
window != null ? window.mOwnerUid : Binder.getCallingUid())
.build();
- surfaceControl.setLayerStack(display.getLayerStack());
if (SHOW_TRANSACTIONS) {
Slog.i(TAG, " THUMBNAIL " + surfaceControl + ": CREATE");
}
@@ -750,10 +749,13 @@ class WindowSurfacePlacer {
anim.restrictDuration(MAX_ANIMATION_DURATION);
anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
- openingAppAnimator.updateThumbnailLayer();
openingAppAnimator.thumbnail = surfaceControl;
openingAppAnimator.thumbnailAnimation = anim;
mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
+
+ // We parent the thumbnail to the app token, and just place it
+ // on top of anything else in the app token.
+ surfaceControl.setLayer(Integer.MAX_VALUE);
} catch (Surface.OutOfResourcesException e) {
Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
+ dirty.width() + " h=" + dirty.height(), e);
diff --git a/com/android/server/wm/WindowTracing.java b/com/android/server/wm/WindowTracing.java
index 5657f6c4..c858b198 100644
--- a/com/android/server/wm/WindowTracing.java
+++ b/com/android/server/wm/WindowTracing.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.os.Build.IS_USER;
import static com.android.server.wm.proto.WindowManagerTraceFileProto.ENTRY;
import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER;
import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
@@ -62,11 +63,16 @@ class WindowTracing {
}
void startTrace(PrintWriter pw) throws IOException {
+ if (IS_USER){
+ logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
+ return;
+ }
synchronized (mLock) {
logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
mWriteQueue.clear();
mTraceFile.delete();
try (OutputStream os = new FileOutputStream(mTraceFile)) {
+ mTraceFile.setReadable(true, false);
ProtoOutputStream proto = new ProtoOutputStream(os);
proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
proto.flush();
@@ -82,6 +88,10 @@ class WindowTracing {
}
void stopTrace(PrintWriter pw) {
+ if (IS_USER){
+ logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
+ return;
+ }
synchronized (mLock) {
logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
mEnabled = mEnabledLockFree = false;
@@ -147,9 +157,11 @@ class WindowTracing {
}
static WindowTracing createDefaultAndStartLooper(Context context) {
- File file = new File("/data/system/window_trace.proto");
+ File file = new File("/data/misc/wmtrace/wm_trace.pb");
WindowTracing windowTracing = new WindowTracing(file);
- new Thread(windowTracing::loop, "window_tracing").start();
+ if (!IS_USER){
+ new Thread(windowTracing::loop, "window_tracing").start();
+ }
return windowTracing;
}
diff --git a/com/android/settingslib/Utils.java b/com/android/settingslib/Utils.java
index 21861692..eb338427 100644
--- a/com/android/settingslib/Utils.java
+++ b/com/android/settingslib/Utils.java
@@ -105,7 +105,8 @@ public class Utils {
}
}
return new UserIconDrawable(iconSize).setIconDrawable(
- UserIcons.getDefaultUserIcon(user.id, /* light= */ false)).bake();
+ UserIcons.getDefaultUserIcon(context.getResources(), user.id, /* light= */ false))
+ .bake();
}
/** Formats a double from 0.0..100.0 with an option to round **/
diff --git a/com/android/settingslib/drawer/CategoryManager.java b/com/android/settingslib/drawer/CategoryManager.java
index ee7885d2..07033304 100644
--- a/com/android/settingslib/drawer/CategoryManager.java
+++ b/com/android/settingslib/drawer/CategoryManager.java
@@ -18,7 +18,6 @@ package com.android.settingslib.drawer;
import android.content.ComponentName;
import android.content.Context;
import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -27,7 +26,6 @@ import android.util.Pair;
import com.android.settingslib.applications.InterestingConfigChanges;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -104,10 +102,10 @@ public class CategoryManager {
}
for (int i = 0; i < mCategories.size(); i++) {
DashboardCategory category = mCategories.get(i);
- for (int j = 0; j < category.tiles.size(); j++) {
- Tile tile = category.tiles.get(j);
+ for (int j = 0; j < category.getTilesCount(); j++) {
+ Tile tile = category.getTile(j);
if (tileBlacklist.contains(tile.intent.getComponent())) {
- category.tiles.remove(j--);
+ category.removeTile(j--);
}
}
}
@@ -181,7 +179,7 @@ public class CategoryManager {
newCategory = new DashboardCategory();
categoryByKeyMap.put(newCategoryKey, newCategory);
}
- newCategory.tiles.add(tile);
+ newCategory.addTile(tile);
}
}
}
@@ -198,7 +196,7 @@ public class CategoryManager {
synchronized void sortCategories(Context context,
Map<String, DashboardCategory> categoryByKeyMap) {
for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
- sortCategoriesForExternalTiles(context, categoryEntry.getValue());
+ categoryEntry.getValue().sortTiles(context.getPackageName());
}
}
@@ -210,16 +208,16 @@ public class CategoryManager {
synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) {
for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
final DashboardCategory category = categoryEntry.getValue();
- final int count = category.tiles.size();
+ final int count = category.getTilesCount();
final Set<ComponentName> components = new ArraySet<>();
for (int i = count - 1; i >= 0; i--) {
- final Tile tile = category.tiles.get(i);
+ final Tile tile = category.getTile(i);
if (tile.intent == null) {
continue;
}
final ComponentName tileComponent = tile.intent.getComponent();
if (components.contains(tileComponent)) {
- category.tiles.remove(i);
+ category.removeTile(i);
} else {
components.add(tileComponent);
}
@@ -234,28 +232,7 @@ public class CategoryManager {
*/
private synchronized void sortCategoriesForExternalTiles(Context context,
DashboardCategory dashboardCategory) {
- final String skipPackageName = context.getPackageName();
+ dashboardCategory.sortTiles(context.getPackageName());
- // Sort tiles based on [priority, package within priority]
- Collections.sort(dashboardCategory.tiles, (tile1, tile2) -> {
- final String package1 = tile1.intent.getComponent().getPackageName();
- final String package2 = tile2.intent.getComponent().getPackageName();
- final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
- // First sort by priority
- final int priorityCompare = tile2.priority - tile1.priority;
- if (priorityCompare != 0) {
- return priorityCompare;
- }
- // Then sort by package name, skip package take precedence
- if (packageCompare != 0) {
- if (TextUtils.equals(package1, skipPackageName)) {
- return -1;
- }
- if (TextUtils.equals(package2, skipPackageName)) {
- return 1;
- }
- }
- return packageCompare;
- });
}
}
diff --git a/com/android/settingslib/drawer/DashboardCategory.java b/com/android/settingslib/drawer/DashboardCategory.java
index f6f81682..a966e824 100644
--- a/com/android/settingslib/drawer/DashboardCategory.java
+++ b/com/android/settingslib/drawer/DashboardCategory.java
@@ -16,6 +16,8 @@
package com.android.settingslib.drawer;
+import static java.lang.String.CASE_INSENSITIVE_ORDER;
+
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,6 +25,8 @@ import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
public class DashboardCategory implements Parcelable {
@@ -48,39 +52,59 @@ public class DashboardCategory implements Parcelable {
/**
* List of the category's children
*/
- public List<Tile> tiles = new ArrayList<>();
-
+ private List<Tile> mTiles = new ArrayList<>();
+
+ DashboardCategory(DashboardCategory in) {
+ if (in != null) {
+ title = in.title;
+ key = in.key;
+ priority = in.priority;
+ for (Tile tile : in.mTiles) {
+ mTiles.add(tile);
+ }
+ }
+ }
public DashboardCategory() {
// Empty
}
+ /**
+ * Get a copy of the list of the category's children.
+ *
+ * Note: the returned list serves as a read-only list. If tiles needs to be added or removed
+ * from the actual tiles list, it should be done through {@link #addTile}, {@link #removeTile}.
+ */
+ public List<Tile> getTiles() {
+ return Collections.unmodifiableList(mTiles);
+ }
+
public void addTile(Tile tile) {
- tiles.add(tile);
+ mTiles.add(tile);
}
public void addTile(int n, Tile tile) {
- tiles.add(n, tile);
+ mTiles.add(n, tile);
}
public void removeTile(Tile tile) {
- tiles.remove(tile);
+ mTiles.remove(tile);
}
public void removeTile(int n) {
- tiles.remove(n);
+ mTiles.remove(n);
}
public int getTilesCount() {
- return tiles.size();
+ return mTiles.size();
}
public Tile getTile(int n) {
- return tiles.get(n);
+ return mTiles.get(n);
}
public boolean containsComponent(ComponentName component) {
- for (Tile tile : tiles) {
+ for (Tile tile : mTiles) {
if (TextUtils.equals(tile.intent.getComponent().getClassName(),
component.getClassName())) {
if (DEBUG) {
@@ -95,6 +119,40 @@ public class DashboardCategory implements Parcelable {
return false;
}
+ /**
+ * Sort priority value for tiles in this category.
+ */
+ public void sortTiles() {
+ Collections.sort(mTiles, TILE_COMPARATOR);
+ }
+
+ /**
+ * Sort priority value and package name for tiles in this category.
+ */
+ public void sortTiles(String skipPackageName) {
+ // Sort mTiles based on [priority, package within priority]
+ Collections.sort(mTiles, (tile1, tile2) -> {
+ final String package1 = tile1.intent.getComponent().getPackageName();
+ final String package2 = tile2.intent.getComponent().getPackageName();
+ final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
+ // First sort by priority
+ final int priorityCompare = tile2.priority - tile1.priority;
+ if (priorityCompare != 0) {
+ return priorityCompare;
+ }
+ // Then sort by package name, skip package take precedence
+ if (packageCompare != 0) {
+ if (TextUtils.equals(package1, skipPackageName)) {
+ return -1;
+ }
+ if (TextUtils.equals(package2, skipPackageName)) {
+ return 1;
+ }
+ }
+ return packageCompare;
+ });
+ }
+
@Override
public int describeContents() {
return 0;
@@ -106,11 +164,11 @@ public class DashboardCategory implements Parcelable {
dest.writeString(key);
dest.writeInt(priority);
- final int count = tiles.size();
+ final int count = mTiles.size();
dest.writeInt(count);
for (int n = 0; n < count; n++) {
- Tile tile = tiles.get(n);
+ Tile tile = mTiles.get(n);
tile.writeToParcel(dest, flags);
}
}
@@ -124,7 +182,7 @@ public class DashboardCategory implements Parcelable {
for (int n = 0; n < count; n++) {
Tile tile = Tile.CREATOR.createFromParcel(in);
- tiles.add(tile);
+ mTiles.add(tile);
}
}
@@ -141,4 +199,13 @@ public class DashboardCategory implements Parcelable {
return new DashboardCategory[size];
}
};
+
+ public static final Comparator<Tile> TILE_COMPARATOR =
+ new Comparator<Tile>() {
+ @Override
+ public int compare(Tile lhs, Tile rhs) {
+ return rhs.priority - lhs.priority;
+ }
+ };
+
}
diff --git a/com/android/settingslib/drawer/TileUtils.java b/com/android/settingslib/drawer/TileUtils.java
index 038dcf84..e986e0f7 100644
--- a/com/android/settingslib/drawer/TileUtils.java
+++ b/com/android/settingslib/drawer/TileUtils.java
@@ -253,7 +253,7 @@ public class TileUtils {
}
ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
for (DashboardCategory category : categories) {
- Collections.sort(category.tiles, TILE_COMPARATOR);
+ category.sortTiles();
}
Collections.sort(categories, CATEGORY_COMPARATOR);
if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
@@ -595,14 +595,6 @@ public class TileUtils {
return pathSegments.get(0);
}
- public static final Comparator<Tile> TILE_COMPARATOR =
- new Comparator<Tile>() {
- @Override
- public int compare(Tile lhs, Tile rhs) {
- return rhs.priority - lhs.priority;
- }
- };
-
private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
new Comparator<DashboardCategory>() {
@Override
diff --git a/com/android/settingslib/drawer/UserAdapter.java b/com/android/settingslib/drawer/UserAdapter.java
index b27d8235..8a09df28 100644
--- a/com/android/settingslib/drawer/UserAdapter.java
+++ b/com/android/settingslib/drawer/UserAdapter.java
@@ -64,7 +64,8 @@ public class UserAdapter implements SpinnerAdapter, ListAdapter {
if (um.getUserIcon(userId) != null) {
icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId));
} else {
- icon = UserIcons.getDefaultUserIcon(userId, /* light= */ false);
+ icon = UserIcons.getDefaultUserIcon(
+ context.getResources(), userId, /* light= */ false);
}
}
this.mIcon = encircle(context, icon);
diff --git a/com/android/setupwizardlib/GlifPatternDrawable.java b/com/android/setupwizardlib/GlifPatternDrawable.java
index 51c1a490..c1d968ae 100644
--- a/com/android/setupwizardlib/GlifPatternDrawable.java
+++ b/com/android/setupwizardlib/GlifPatternDrawable.java
@@ -23,7 +23,6 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
-import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
@@ -96,7 +95,6 @@ public class GlifPatternDrawable extends Drawable {
private int mColor;
private Paint mTempPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private ColorFilter mColorFilter;
public GlifPatternDrawable(int color) {
setColor(color);
@@ -140,17 +138,10 @@ public class GlifPatternDrawable extends Drawable {
canvas.clipRect(bounds);
scaleCanvasToBounds(canvas, bitmap, bounds);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
- && canvas.isHardwareAccelerated()) {
- mTempPaint.setColorFilter(mColorFilter);
- canvas.drawBitmap(bitmap, 0, 0, mTempPaint);
- } else {
- // Software renderer doesn't work properly with ColorMatrix filter on ALPHA_8 bitmaps.
- canvas.drawColor(Color.BLACK);
- mTempPaint.setColor(Color.WHITE);
- canvas.drawBitmap(bitmap, 0, 0, mTempPaint);
- canvas.drawColor(mColor);
- }
+ canvas.drawColor(Color.BLACK);
+ mTempPaint.setColor(Color.WHITE);
+ canvas.drawBitmap(bitmap, 0, 0, mTempPaint);
+ canvas.drawColor(mColor);
canvas.restore();
}
@@ -299,12 +290,6 @@ public class GlifPatternDrawable extends Drawable {
final int g = Color.green(color);
final int b = Color.blue(color);
mColor = Color.argb(COLOR_ALPHA_INT, r, g, b);
- mColorFilter = new ColorMatrixColorFilter(new float[] {
- 0, 0, 0, 1 - COLOR_ALPHA, r * COLOR_ALPHA,
- 0, 0, 0, 1 - COLOR_ALPHA, g * COLOR_ALPHA,
- 0, 0, 0, 1 - COLOR_ALPHA, b * COLOR_ALPHA,
- 0, 0, 0, 0, 255
- });
invalidateSelf();
}
diff --git a/com/android/systemui/OverviewProxyService.java b/com/android/systemui/OverviewProxyService.java
index 2e4a5a41..22922e7b 100644
--- a/com/android/systemui/OverviewProxyService.java
+++ b/com/android/systemui/OverviewProxyService.java
@@ -22,11 +22,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.graphics.Bitmap;
import android.graphics.Rect;
-import android.net.Uri;
import android.os.Binder;
-import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -36,12 +33,14 @@ import android.os.UserHandle;
import android.util.Log;
import android.view.SurfaceControl;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.shared.system.GraphicBufferCompat;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -67,12 +66,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private int mConnectionBackoffAttempts;
private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
- public Bitmap screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
- boolean useIdentityTransform, int rotation) {
+ public GraphicBufferCompat screenshot(Rect sourceCrop, int width, int height, int minLayer,
+ int maxLayer, boolean useIdentityTransform, int rotation) {
long token = Binder.clearCallingIdentity();
try {
- return SurfaceControl.screenshot(sourceCrop, width, height, minLayer, maxLayer,
- useIdentityTransform, rotation);
+ return new GraphicBufferCompat(SurfaceControl.screenshotToBuffer(sourceCrop, width,
+ height, minLayer, maxLayer, useIdentityTransform, rotation));
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/com/android/systemui/SystemUIFactory.java b/com/android/systemui/SystemUIFactory.java
index 0c067ff3..526a8f46 100644
--- a/com/android/systemui/SystemUIFactory.java
+++ b/com/android/systemui/SystemUIFactory.java
@@ -28,6 +28,7 @@ import com.android.systemui.Dependency.DependencyProvider;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockIcon;
@@ -86,10 +87,10 @@ public class SystemUIFactory {
public ScrimController createScrimController(LightBarController lightBarController,
ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
- LockscreenWallpaper lockscreenWallpaper,
- Consumer<Boolean> scrimVisibleListener) {
+ LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener,
+ DozeParameters dozeParameters) {
return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
- scrimVisibleListener);
+ scrimVisibleListener, dozeParameters);
}
public NotificationIconAreaController createNotificationIconAreaController(Context context,
diff --git a/com/android/systemui/doze/DozeHost.java b/com/android/systemui/doze/DozeHost.java
index 7db118d7..2f607eee 100644
--- a/com/android/systemui/doze/DozeHost.java
+++ b/com/android/systemui/doze/DozeHost.java
@@ -35,7 +35,6 @@ public interface DozeHost {
boolean isBlockingDoze();
void startPendingIntentDismissingKeyguard(PendingIntent intent);
- void abortPulsing();
void extendPulse();
void setAnimateWakeup(boolean animateWakeup);
diff --git a/com/android/systemui/keyguard/KeyguardSliceProvider.java b/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 03018f7d..6ddc76b5 100644
--- a/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -84,7 +84,7 @@ public class KeyguardSliceProvider extends SliceProvider {
@Override
public Slice onBindSlice(Uri sliceUri) {
- return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build();
+ return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build();
}
@Override
diff --git a/com/android/systemui/keyguard/KeyguardViewMediator.java b/com/android/systemui/keyguard/KeyguardViewMediator.java
index a35ba9fa..c92acd06 100644
--- a/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -60,7 +60,7 @@ import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.view.ViewGroup;
-import android.view.WindowManagerPolicy;
+import android.view.WindowManagerPolicyConstants;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -129,7 +129,7 @@ import java.util.ArrayList;
* false, this will override all other conditions for turning on the keyguard.
*
* Threading and synchronization:
- * This class is created by the initialization routine of the {@link android.view.WindowManagerPolicy},
+ * This class is created by the initialization routine of the {@link WindowManagerPolicyConstants},
* and runs on its thread. The keyguard UI is created from that thread in the
* constructor of this class. The apis may be called from other threads, including the
* {@link com.android.server.input.InputManagerService}'s and {@link android.view.WindowManager}'s.
@@ -766,8 +766,8 @@ public class KeyguardViewMediator extends SystemUI {
/**
* Called to let us know the screen was turned off.
- * @param why either {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_USER} or
- * {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
+ * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER} or
+ * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}.
*/
public void onStartedGoingToSleep(int why) {
if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")");
@@ -797,8 +797,8 @@ public class KeyguardViewMediator extends SystemUI {
}
} else if (mShowing) {
mPendingReset = true;
- } else if ((why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT && timeout > 0)
- || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) {
+ } else if ((why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT && timeout > 0)
+ || (why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER && !lockImmediately)) {
doKeyguardLaterLocked(timeout);
mLockLater = true;
} else if (!mLockPatternUtils.isLockScreenDisabled(currentUser)) {
@@ -1031,7 +1031,7 @@ public class KeyguardViewMediator extends SystemUI {
}
/**
- * Same semantics as {@link android.view.WindowManagerPolicy#enableKeyguard}; provide
+ * Same semantics as {@link WindowManagerPolicyConstants#enableKeyguard}; provide
* a way for external stuff to override normal keyguard behavior. For instance
* the phone app disables the keyguard when it receives incoming calls.
*/
@@ -1780,13 +1780,13 @@ public class KeyguardViewMediator extends SystemUI {
int flags = 0;
if (mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock()
|| mWakeAndUnlocking) {
- flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
+ flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
}
if (mStatusBarKeyguardViewManager.isGoingToNotificationShade()) {
- flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
+ flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
}
if (mStatusBarKeyguardViewManager.isUnlockWithWallpaper()) {
- flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+ flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
}
mUpdateMonitor.setKeyguardGoingAway(true /* goingAway */);
@@ -2028,12 +2028,9 @@ public class KeyguardViewMediator extends SystemUI {
}
public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
- ViewGroup container,
- ScrimController scrimController,
- FingerprintUnlockController fingerprintUnlockController) {
+ ViewGroup container, FingerprintUnlockController fingerprintUnlockController) {
mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container,
- scrimController, fingerprintUnlockController,
- mDismissCallbackRegistry);
+ fingerprintUnlockController, mDismissCallbackRegistry);
return mStatusBarKeyguardViewManager;
}
diff --git a/com/android/systemui/pip/phone/PipTouchHandler.java b/com/android/systemui/pip/phone/PipTouchHandler.java
index 2b48e0fb..51175d1d 100644
--- a/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -387,7 +387,9 @@ public class PipTouchHandler {
}
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE: {
- if (mAccessibilityManager.isEnabled() && !mSendingHoverAccessibilityEvents) {
+ if (mAccessibilityManager.isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_HOVER_ENTER)
+ && !mSendingHoverAccessibilityEvents) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
event.setImportantForAccessibility(true);
@@ -400,7 +402,9 @@ public class PipTouchHandler {
break;
}
case MotionEvent.ACTION_HOVER_EXIT: {
- if (mAccessibilityManager.isEnabled() && mSendingHoverAccessibilityEvents) {
+ if (mAccessibilityManager.isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_HOVER_EXIT)
+ && mSendingHoverAccessibilityEvents) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
event.setImportantForAccessibility(true);
diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java
index eef43d29..a9846801 100644
--- a/com/android/systemui/pip/tv/PipManager.java
+++ b/com/android/systemui/pip/tv/PipManager.java
@@ -625,9 +625,7 @@ public class PipManager implements BasePipManager {
@Override
public void onTaskStackChanged() {
if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
- if (!checkCurrentUserId(mContext, DEBUG)) {
- return;
- }
+
if (getState() != STATE_NO_PIP) {
boolean hasPip = false;
@@ -662,9 +660,7 @@ public class PipManager implements BasePipManager {
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
if (DEBUG) Log.d(TAG, "onActivityPinned()");
- if (!checkCurrentUserId(mContext, DEBUG)) {
- return;
- }
+
StackInfo stackInfo = getPinnedStackInfo();
if (stackInfo == null) {
Log.w(TAG, "Cannot find pinned stack");
@@ -690,9 +686,7 @@ public class PipManager implements BasePipManager {
@Override
public void onPinnedActivityRestartAttempt(boolean clearedTask) {
if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
- if (!checkCurrentUserId(mContext, DEBUG)) {
- return;
- }
+
// If PIPed activity is launched again by Launcher or intent, make it fullscreen.
movePipToFullscreen();
}
@@ -700,9 +694,7 @@ public class PipManager implements BasePipManager {
@Override
public void onPinnedStackAnimationEnded() {
if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
- if (!checkCurrentUserId(mContext, DEBUG)) {
- return;
- }
+
switch (getState()) {
case STATE_PIP_MENU:
showPipMenu();
diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java
index 3b1b2f90..663f2067 100644
--- a/com/android/systemui/recents/RecentsImpl.java
+++ b/com/android/systemui/recents/RecentsImpl.java
@@ -23,14 +23,12 @@ import static android.view.View.MeasureSpec;
import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
import android.app.ActivityManager;
-import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
@@ -89,7 +87,6 @@ import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFutur
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.DividerView;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
@@ -156,7 +153,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Launched from app is always the worst case (in terms of how many
// thumbnails/tasks visible)
launchState.launchedFromApp = true;
- mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState);
+ mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState,
+ -1 /* lastScrollPPresent */);
VisibilityReport visibilityReport =
mBackgroundLayoutAlgorithm.computeStackVisibilityReport(
stack.getTasks());
@@ -656,13 +654,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// the resize mode already.
if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) {
EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
- showRecents(
- false /* triggeredFromAltTab */,
- dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
- false /* animate */,
- true /* launchedWhileDockingTask*/,
- false /* fromHome */,
- DividerView.INVALID_RECENTS_GROW_TARGET);
}
}
diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java
index d89bab75..2d3080b1 100644
--- a/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -21,12 +21,10 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityOptions;
@@ -49,9 +47,6 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.IRemoteCallback;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -63,7 +58,6 @@ import android.service.dreams.IDreamManager;
import android.util.Log;
import android.util.MutableBoolean;
import android.view.Display;
-import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IDockedStackListener;
import android.view.IWindowManager;
import android.view.WindowManager;
@@ -74,16 +68,12 @@ import android.view.accessibility.AccessibilityManager;
import com.android.internal.app.AssistUtils;
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.recents.Recents;
import com.android.systemui.recents.RecentsImpl;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.policy.UserInfoController;
import java.util.List;
-import java.util.function.Consumer;
/**
* Acts as a shim around the real system services that we need to access data from, and provides
@@ -268,22 +258,6 @@ public class SystemServicesProxy {
return mIsSafeMode;
}
- /** Docks a task to the side of the screen and starts it. */
- public boolean startTaskInDockedMode(int taskId, int createMode) {
- if (mIam == null) return false;
-
- try {
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setSplitScreenCreateMode(createMode);
- options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- mIam.startActivityFromRecents(taskId, options.toBundle());
- return true;
- } catch (Exception e) {
- Log.e(TAG, "Failed to dock task: " + taskId + " with createMode: " + createMode, e);
- }
- return false;
- }
-
/** Moves an already resumed task to the side of the screen to initiate split screen. */
public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
Rect initialBounds) {
@@ -397,7 +371,7 @@ public class SystemServicesProxy {
if (mIam == null) return false;
try {
- return mIam.isInLockTaskMode();
+ return mIam.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED;
} catch (RemoteException e) {
return false;
}
@@ -540,16 +514,6 @@ public class SystemServicesProxy {
}
}
- public void overridePendingAppTransitionMultiThumbFuture(
- IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener,
- boolean scaleUp) {
- try {
- mIwm.overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, scaleUp);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to override transition: " + e);
- }
- }
-
/**
* Updates the visibility of recents.
*/
diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java
index 1440fc16..e3ed1aaa 100644
--- a/com/android/systemui/recents/views/RecentsView.java
+++ b/com/android/systemui/recents/views/RecentsView.java
@@ -16,13 +16,14 @@
package com.android.systemui.recents.views;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+
import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
@@ -33,11 +34,11 @@ import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.IRemoteCallback;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
-import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -86,13 +87,15 @@ 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.shared.recents.utilities.Utilities;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -608,16 +611,17 @@ public class RecentsView extends FrameLayout {
// rect to its final layout-space rect
Utilities.setViewFrameFromTranslation(event.taskView);
- // Dock the task and launch it
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) {
+ final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions(
+ dockState.createMode == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+ if (ActivityManagerWrapper.getInstance().startActivityFromRecents(event.task.key.id,
+ options)) {
final Runnable animStartedListener = () -> {
EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
- // Remove the task and don't bother relaying out, as all the tasks will be
- // relaid out when the stack changes on the multiwindow change event
+ // Remove the task and don't bother relaying out, as all the tasks
+ // will be relaid out when the stack changes on the multiwindow
+ // change event
getStack().removeTask(event.task, null, true /* fromDockGesture */);
};
-
final Rect taskRect = getTaskRect(event.taskView);
AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(
getHandler()) {
@@ -626,10 +630,8 @@ public class RecentsView extends FrameLayout {
return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect);
}
};
- ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
- RecentsTransition.wrapStartedListener(getHandler(), animStartedListener),
- true /* scaleUp */);
-
+ WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
+ future, animStartedListener, getHandler(), true /* scaleUp */);
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
event.task.getTopComponent().flattenToShortString());
} else {
@@ -1032,11 +1034,9 @@ public class RecentsView extends FrameLayout {
if (taskIndex > -1) {
taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
}
- EventBus.getDefault().send(new LaunchTaskSucceededEvent(
- taskIndexFromFront));
+ EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
} else {
- Log.e(TAG, mContext.getString(R.string.recents_launch_error_message,
- task.title));
+ Log.e(TAG, mContext.getString(R.string.recents_launch_error_message, task.title));
// Dismiss the task if we fail to launch it
if (taskView != null) {
diff --git a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 600da041..d9f79bb6 100644
--- a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -431,7 +431,7 @@ public class TaskStackLayoutAlgorithm {
* in the stack.
*/
public void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet,
- RecentsActivityLaunchState launchState) {
+ RecentsActivityLaunchState launchState, float lastScrollPPercent) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Clear the progress map
@@ -506,6 +506,8 @@ public class TaskStackLayoutAlgorithm {
if (launchState.launchedWithAltTab) {
mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
+ } else if (0 <= lastScrollPPercent && lastScrollPPercent <= 1) {
+ mInitialScrollP = Utilities.mapRange(lastScrollPPercent, mMinScrollP, mMaxScrollP);
} else if (Recents.getConfiguration().isLowRamDevice) {
mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
scrollToFront);
diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java
index 11975012..36c9095f 100644
--- a/com/android/systemui/recents/views/TaskStackView.java
+++ b/com/android/systemui/recents/views/TaskStackView.java
@@ -209,6 +209,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
private int mLastHeight;
private boolean mStackActionButtonVisible;
+ // Percentage of last ScrollP from the min to max scrollP that lives after configuration changes
+ private float mLastScrollPPercent;
+
// We keep track of the task view focused by user interaction and draw a frame around it in the
// grid layout.
private TaskViewFocusFrame mTaskViewFocusFrame;
@@ -327,6 +330,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mStackScroller.reset();
mStableLayoutAlgorithm.reset();
mLayoutAlgorithm.reset();
+ mLastScrollPPercent = -1;
}
// Since we always animate to the same place in (the initial state), always reset the stack
@@ -822,7 +826,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
RecentsActivityLaunchState launchState) {
// Compute the min and max scroll values
- mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState);
+ mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState, mLastScrollPPercent);
if (boundScrollToNewMinMax) {
mStackScroller.boundScroll();
@@ -1150,6 +1154,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (mTaskViewsClipDirty) {
clipTaskViews();
}
+ mLastScrollPPercent = Utilities.clamp(Utilities.unmapRange(mStackScroller.getStackScroll(),
+ mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP), 0, 1);
}
/**
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
index 8e2a25c1..4834bb18 100644
--- a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
@@ -45,6 +45,11 @@ import java.util.List;
*/
public class RecentsTaskLoadPlan {
+ /** The set of conditions to preload tasks. */
+ public static class PreloadOptions {
+ public boolean loadTitles = true;
+ }
+
/** The set of conditions to load tasks. */
public static class Options {
public int runningTaskId = -1;
@@ -80,7 +85,8 @@ public class RecentsTaskLoadPlan {
* 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).
*/
- public void preloadPlan(RecentsTaskLoader loader, int runningTaskId, int currentUserId) {
+ public void preloadPlan(PreloadOptions opts, RecentsTaskLoader loader, int runningTaskId,
+ int currentUserId) {
Resources res = mContext.getResources();
ArrayList<Task> allTasks = new ArrayList<>();
if (mRawTasks == null) {
@@ -110,9 +116,12 @@ public class RecentsTaskLoadPlan {
}
// Load the title, icon, and color
- String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
- String titleDescription = loader.getAndUpdateContentDescription(taskKey,
- t.taskDescription);
+ String title = opts.loadTitles
+ ? loader.getAndUpdateActivityTitle(taskKey, t.taskDescription)
+ : "";
+ String titleDescription = opts.loadTitles
+ ? loader.getAndUpdateContentDescription(taskKey, t.taskDescription)
+ : "";
Drawable icon = isStackTask
? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
: null;
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
index 9a991cfa..0f68026c 100644
--- a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
@@ -32,6 +32,7 @@ import android.util.LruCache;
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
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;
@@ -155,7 +156,7 @@ public class RecentsTaskLoader {
int currentUserId) {
try {
Trace.beginSection("preloadPlan");
- plan.preloadPlan(this, runningTaskId, currentUserId);
+ plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId);
} finally {
Trace.endSection();
}
diff --git a/com/android/systemui/shared/recents/utilities/AppTrace.java b/com/android/systemui/shared/recents/utilities/AppTrace.java
new file mode 100644
index 00000000..0241c593
--- /dev/null
+++ b/com/android/systemui/shared/recents/utilities/AppTrace.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.utilities;
+
+import static android.os.Trace.TRACE_TAG_APP;
+
+/**
+ * Helper class for internal trace functions.
+ */
+public class AppTrace {
+
+ /**
+ * Begins a new async trace section with the given {@param key} and {@param cookie}.
+ */
+ public static void start(String key, int cookie) {
+ android.os.Trace.asyncTraceBegin(TRACE_TAG_APP, key, cookie);
+ }
+
+ /**
+ * Begins a new async trace section with the given {@param key}.
+ */
+ public static void start(String key) {
+ android.os.Trace.asyncTraceBegin(TRACE_TAG_APP, key, 0);
+ }
+
+ /**
+ * Ends an existing async trace section with the given {@param key}.
+ */
+ public static void end(String key) {
+ android.os.Trace.asyncTraceEnd(TRACE_TAG_APP, key, 0);
+ }
+
+ /**
+ * Ends an existing async trace section with the given {@param key} and {@param cookie}.
+ */
+ public static void end(String key, int cookie) {
+ android.os.Trace.asyncTraceEnd(TRACE_TAG_APP, key, cookie);
+ }
+
+ /**
+ * Begins a new trace section with the given {@param key}. Can be nested.
+ */
+ public static void beginSection(String key) {
+ android.os.Trace.beginSection(key);
+ }
+
+ /**
+ * Ends an existing trace section started in the last {@link #beginSection(String)}.
+ */
+ public static void endSection() {
+ android.os.Trace.endSection();
+ }
+
+ /**
+ * Traces a counter value.
+ */
+ public static void count(String name, int count) {
+ android.os.Trace.traceCounter(TRACE_TAG_APP, name, count);
+ }
+}
diff --git a/com/android/systemui/shared/system/ActivityManagerWrapper.java b/com/android/systemui/shared/system/ActivityManagerWrapper.java
index f6fab86c..eb2d12ed 100644
--- a/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -319,32 +319,39 @@ public class ActivityManagerWrapper {
mBackgroundExecutor.submit(new Runnable() {
@Override
public void run() {
+ boolean result = false;
try {
- ActivityManager.getService().startActivityFromRecents(taskKey.id,
- finalOptions == null ? null : finalOptions.toBundle());
- if (resultCallback != null) {
- resultCallbackHandler.post(new Runnable() {
- @Override
- public void run() {
- resultCallback.accept(true);
- }
- });
- }
+ result = startActivityFromRecents(taskKey.id, finalOptions);
} catch (Exception e) {
- if (resultCallback != null) {
- resultCallbackHandler.post(new Runnable() {
- @Override
- public void run() {
- resultCallback.accept(false);
- }
- });
- }
+ // Fall through
+ }
+ final boolean finalResult = result;
+ if (resultCallback != null) {
+ resultCallbackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ resultCallback.accept(finalResult);
+ }
+ });
}
}
});
}
/**
+ * Starts a task from Recents synchronously.
+ */
+ public boolean startActivityFromRecents(int taskId, ActivityOptions options) {
+ try {
+ Bundle optsBundle = options == null ? null : options.toBundle();
+ ActivityManager.getService().startActivityFromRecents(taskId, optsBundle);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
* Registers a task stack listener with the system.
* This should be called on the main thread.
*/
diff --git a/com/android/systemui/shared/system/ActivityOptionsCompat.java b/com/android/systemui/shared/system/ActivityOptionsCompat.java
new file mode 100644
index 00000000..705a2152
--- /dev/null
+++ b/com/android/systemui/shared/system/ActivityOptionsCompat.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+
+import android.app.ActivityOptions;
+
+/**
+ * Wrapper around internal ActivityOptions creation.
+ */
+public abstract class ActivityOptionsCompat {
+
+ /**
+ * @return ActivityOptions for starting a task in split screen.
+ */
+ public static ActivityOptions makeSplitScreenOptions(boolean dockTopLeft) {
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ options.setSplitScreenCreateMode(dockTopLeft
+ ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+ : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
+ return options;
+ }
+}
diff --git a/com/android/systemui/shared/system/BackgroundExecutor.java b/com/android/systemui/shared/system/BackgroundExecutor.java
index cfd1f9a5..0bd89a78 100644
--- a/com/android/systemui/shared/system/BackgroundExecutor.java
+++ b/com/android/systemui/shared/system/BackgroundExecutor.java
@@ -16,6 +16,7 @@
package com.android.systemui.shared.system;
+import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -37,6 +38,13 @@ public class BackgroundExecutor {
}
/**
+ * Runs the given {@param callable} on one of the background executor threads.
+ */
+ public <T> Future<T> submit(Callable<T> callable) {
+ return mExecutorService.submit(callable);
+ }
+
+ /**
* Runs the given {@param runnable} on one of the background executor threads.
*/
public Future<?> submit(Runnable runnable) {
diff --git a/com/android/systemui/shared/system/ChoreographerCompat.java b/com/android/systemui/shared/system/ChoreographerCompat.java
new file mode 100644
index 00000000..4d422bb8
--- /dev/null
+++ b/com/android/systemui/shared/system/ChoreographerCompat.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.view.Choreographer.CALLBACK_INPUT;
+
+import android.view.Choreographer;
+
+/**
+ * Wraps the internal choreographer.
+ */
+public class ChoreographerCompat {
+
+ /**
+ * Posts an input callback to the choreographer.
+ */
+ public static void postInputFrame(Choreographer choreographer, Runnable runnable) {
+ choreographer.postCallback(CALLBACK_INPUT, runnable, null);
+ }
+}
diff --git a/com/android/systemui/shared/system/GraphicBufferCompat.java b/com/android/systemui/shared/system/GraphicBufferCompat.java
new file mode 100644
index 00000000..66b8fed1
--- /dev/null
+++ b/com/android/systemui/shared/system/GraphicBufferCompat.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.system;
+
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Wraps the internal graphic buffer.
+ */
+public class GraphicBufferCompat implements Parcelable {
+
+ private GraphicBuffer mBuffer;
+
+ public GraphicBufferCompat(GraphicBuffer buffer) {
+ mBuffer = buffer;
+ }
+
+ public GraphicBufferCompat(Parcel in) {
+ mBuffer = GraphicBuffer.CREATOR.createFromParcel(in);
+ }
+
+ public Bitmap toBitmap() {
+ return mBuffer != null
+ ? Bitmap.createHardwareBitmap(mBuffer)
+ : null;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mBuffer.writeToParcel(dest, flags);
+ }
+
+ public static final Parcelable.Creator<GraphicBufferCompat> CREATOR
+ = new Parcelable.Creator<GraphicBufferCompat>() {
+ public GraphicBufferCompat createFromParcel(Parcel in) {
+ return new GraphicBufferCompat(in);
+ }
+
+ public GraphicBufferCompat[] newArray(int size) {
+ return new GraphicBufferCompat[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/com/android/systemui/shared/system/WindowManagerWrapper.java b/com/android/systemui/shared/system/WindowManagerWrapper.java
index 1477558a..225dbb4a 100644
--- a/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -19,8 +19,15 @@ package com.android.systemui.shared.system;
import static android.view.Display.DEFAULT_DISPLAY;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.WindowManagerGlobal;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+
public class WindowManagerWrapper {
private static final String TAG = "WindowManagerWrapper";
@@ -38,8 +45,24 @@ public class WindowManagerWrapper {
try {
WindowManagerGlobal.getWindowManagerService().getStableInsets(DEFAULT_DISPLAY,
outStableInsets);
- } catch (Exception e) {
- e.printStackTrace();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get stable insets", e);
+ }
+ }
+
+ /**
+ * Overrides a pending app transition.
+ */
+ public void overridePendingAppTransitionMultiThumbFuture(
+ AppTransitionAnimationSpecsFuture animationSpecFuture,
+ Runnable animStartedCallback, Handler animStartedCallbackHandler, boolean scaleUp) {
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionMultiThumbFuture(animationSpecFuture.getFuture(),
+ RecentsTransition.wrapStartedListener(animStartedCallbackHandler,
+ animStartedCallback), scaleUp);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to override pending app transition (multi-thumbnail future): ", e);
}
}
}
diff --git a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 1cda3011..da798848 100644
--- a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -20,6 +20,8 @@ import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIG
import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.os.UserHandle.USER_CURRENT;
+import static com.android.systemui.statusbar.phone.NavigationBarGestureHelper.DRAG_MODE_NONE;
+
import android.app.ActivityManager;
import android.content.res.Configuration;
import android.os.RemoteException;
@@ -36,6 +38,7 @@ 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;
@@ -89,20 +92,11 @@ public class ShortcutKeyDispatcher extends SystemUI
try {
int dockSide = mWindowManagerService.getDockedStackSide();
if (dockSide == WindowManager.DOCKED_INVALID) {
- // If there is no window docked, we dock the top-most window.
+ // Split the screen
Recents recents = getComponent(Recents.class);
- int dockMode = (shortcutCode == SC_DOCK_LEFT)
+ recents.splitPrimaryTask(DRAG_MODE_NONE, (shortcutCode == SC_DOCK_LEFT)
? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
- : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
- List<ActivityManager.RecentTaskInfo> taskList =
- ActivityManagerWrapper.getInstance().getRecentTasks(1, USER_CURRENT);
- recents.showRecentApps(
- false /* triggeredFromAltTab */,
- false /* fromHome */);
- if (!taskList.isEmpty()) {
- SystemServicesProxy.getInstance(mContext).startTaskInDockedMode(
- taskList.get(0).id, dockMode);
- }
+ : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
} else {
// If there is already a docked window, we respond by resizing the docking pane.
DividerView dividerView = getComponent(Divider.class).getView();
diff --git a/com/android/systemui/statusbar/ActivatableNotificationView.java b/com/android/systemui/statusbar/ActivatableNotificationView.java
index 84b7015f..ff0357a3 100644
--- a/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -695,6 +695,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f);
}
+ protected void updateBackgroundClipping() {
+ mBackgroundNormal.setBottomAmountClips(!isChildInGroup());
+ mBackgroundDimmed.setBottomAmountClips(!isChildInGroup());
+ }
+
protected boolean shouldHideBackground() {
return mDark;
}
@@ -901,12 +906,45 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
contentView.setAlpha(contentAlpha);
}
+ @Override
+ protected void applyRoundness() {
+ super.applyRoundness();
+ applyBackgroundRoundness(getCurrentBackgroundRadiusTop(),
+ getCurrentBackgroundRadiusBottom());
+ }
+
+ protected void applyBackgroundRoundness(float topRadius, float bottomRadius) {
+ mBackgroundDimmed.setRoundness(topRadius, bottomRadius);
+ mBackgroundNormal.setRoundness(topRadius, bottomRadius);
+ }
+
+ @Override
+ protected void setBackgroundTop(int backgroundTop) {
+ mBackgroundDimmed.setBackgroundTop(backgroundTop);
+ mBackgroundNormal.setBackgroundTop(backgroundTop);
+ }
+
protected abstract View getContentView();
public int calculateBgColor() {
return calculateBgColor(true /* withTint */, true /* withOverRide */);
}
+ @Override
+ public void setCurrentSidePaddings(float currentSidePaddings) {
+ super.setCurrentSidePaddings(currentSidePaddings);
+ mBackgroundNormal.setCurrentSidePaddings(currentSidePaddings);
+ mBackgroundDimmed.setCurrentSidePaddings(currentSidePaddings);
+ }
+
+ @Override
+ protected boolean childNeedsClipping(View child) {
+ if (child instanceof NotificationBackgroundView && isClippingNeeded()) {
+ return true;
+ }
+ return super.childNeedsClipping(child);
+ }
+
/**
* @param withTint should a possible tint be factored in?
* @param withOverRide should the value be interpolated with {@link #mOverrideTint}
diff --git a/com/android/systemui/statusbar/CommandQueue.java b/com/android/systemui/statusbar/CommandQueue.java
index 63492750..8e1b1043 100644
--- a/com/android/systemui/statusbar/CommandQueue.java
+++ b/com/android/systemui/statusbar/CommandQueue.java
@@ -82,6 +82,7 @@ public class CommandQueue extends IStatusBar.Stub {
private static final int MSG_TOGGLE_PANEL = 35 << MSG_SHIFT;
private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT;
private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT;
+ private static final int MSG_ROTATION_PROPOSAL = 38 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -142,6 +143,8 @@ public class CommandQueue extends IStatusBar.Stub {
default void handleSystemKey(int arg1) { }
default void handleShowGlobalActionsMenu() { }
default void handleShowShutdownUi(boolean isReboot, String reason) { }
+
+ default void onRotationProposal(int rotation) { }
}
@VisibleForTesting
@@ -458,6 +461,15 @@ public class CommandQueue extends IStatusBar.Stub {
}
}
+ @Override
+ public void onProposedRotationChanged(int rotation) {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_ROTATION_PROPOSAL);
+ mHandler.obtainMessage(MSG_ROTATION_PROPOSAL, rotation, 0,
+ null).sendToTarget();
+ }
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -654,6 +666,11 @@ public class CommandQueue extends IStatusBar.Stub {
mCallbacks.get(i).setTopAppHidesStatusBar(msg.arg1 != 0);
}
break;
+ case MSG_ROTATION_PROPOSAL:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onRotationProposal(msg.arg1);
+ }
+ break;
}
}
}
diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 8ff950ed..23d9caee 100644
--- a/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -26,6 +26,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Configuration;
+import android.graphics.Path;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -65,7 +66,6 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.NotificationGuts.GutsContent;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
-import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -102,7 +102,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private int mIconTransformContentShift;
private int mIconTransformContentShiftNoIcon;
private int mNotificationMinHeightLegacy;
+ private int mNotificationMinHeightBeforeP;
private int mMaxHeadsUpHeightLegacy;
+ private int mMaxHeadsUpHeightBeforeP;
private int mMaxHeadsUpHeight;
private int mMaxHeadsUpHeightIncreased;
private int mNotificationMinHeight;
@@ -435,9 +437,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
boolean customView = layout.getContractedChild().getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
+ boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
int minHeight;
- if (customView && beforeN && !mIsSummaryWithChildren) {
- minHeight = mNotificationMinHeightLegacy;
+ if (customView && beforeP && !mIsSummaryWithChildren) {
+ minHeight = beforeN ? mNotificationMinHeightLegacy : mNotificationMinHeightBeforeP;
} else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
minHeight = mNotificationMinHeightLarge;
} else {
@@ -447,8 +450,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
layout.getHeadsUpChild().getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
int headsUpheight;
- if (headsUpCustom && beforeN) {
- headsUpheight = mMaxHeadsUpHeightLegacy;
+ if (headsUpCustom && beforeP) {
+ headsUpheight = beforeN ? mMaxHeadsUpHeightLegacy : mMaxHeadsUpHeightBeforeP;
} else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
headsUpheight = mMaxHeadsUpHeightIncreased;
} else {
@@ -535,6 +538,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
onChildrenCountChanged();
row.setIsChildInGroup(false, null);
+ row.setBottomRoundness(0.0f, false /* animate */);
}
@Override
@@ -563,6 +567,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mNotificationParent.updateBackgroundForGroupState();
}
updateIconVisibilities();
+ updateBackgroundClipping();
}
@Override
@@ -916,6 +921,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
addView(mMenuRow.getMenuView(), menuIndex);
}
for (NotificationContentView l : mLayouts) {
+ l.initView();
l.reInflateViews();
}
mNotificationInflater.onDensityOrFontScaleChanged();
@@ -1025,6 +1031,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mKeepInParent = keepInParent;
}
+ @Override
public boolean isRemoved() {
return mRemoved;
}
@@ -1264,6 +1271,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private void initDimens() {
mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_legacy);
+ mNotificationMinHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height_before_p);
mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height);
mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
@@ -1274,6 +1283,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
R.dimen.notification_ambient_height);
mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_legacy);
+ mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_heads_up_height_before_p);
mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height);
mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
@@ -1752,6 +1763,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mPrivateLayout.updateExpandButtons(isExpandable());
updateChildrenHeaderAppearance();
updateChildrenVisibility();
+ applyChildrenRoundness();
}
public void updateChildrenHeaderAppearance() {
@@ -2332,6 +2344,56 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ @Override
+ protected boolean childNeedsClipping(View child) {
+ if (child instanceof NotificationContentView) {
+ NotificationContentView contentView = (NotificationContentView) child;
+ if (isClippingNeeded()) {
+ return true;
+ } else if (!hasNoRoundingAndNoPadding() && contentView.shouldClipToSidePaddings()) {
+ return true;
+ }
+ } else if (child == mChildrenContainer) {
+ if (isClippingNeeded() || ((isGroupExpanded() || isGroupExpansionChanging())
+ && getClipBottomAmount() != 0.0f && getCurrentBottomRoundness() != 0.0f)) {
+ return true;
+ }
+ } else if (child instanceof NotificationGuts) {
+ return !hasNoRoundingAndNoPadding();
+ }
+ return super.childNeedsClipping(child);
+ }
+
+ @Override
+ protected void applyRoundness() {
+ super.applyRoundness();
+ applyChildrenRoundness();
+ }
+
+ private void applyChildrenRoundness() {
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness());
+ }
+ }
+
+ @Override
+ public Path getCustomClipPath(View child) {
+ if (child instanceof NotificationGuts) {
+ return getClipPath(true, /* ignoreTranslation */
+ false /* clipRoundedToBottom */);
+ }
+ if (child instanceof NotificationChildrenContainer) {
+ return getClipPath(false, /* ignoreTranslation */
+ true /* clipRoundedToBottom */);
+ }
+ return super.getCustomClipPath(child);
+ }
+
+ private boolean hasNoRoundingAndNoPadding() {
+ return mCurrentSidePaddings == 0 && getCurrentBottomRoundness() == 0.0f
+ && getCurrentTopRoundness() == 0.0f;
+ }
+
public boolean isShowingAmbient() {
return mShowAmbient;
}
@@ -2344,6 +2406,20 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ @Override
+ public void setCurrentSidePaddings(float currentSidePaddings) {
+ if (mIsSummaryWithChildren) {
+ List<ExpandableNotificationRow> notificationChildren =
+ mChildrenContainer.getNotificationChildren();
+ int size = notificationChildren.size();
+ for (int i = 0; i < size; i++) {
+ ExpandableNotificationRow row = notificationChildren.get(i);
+ row.setCurrentSidePaddings(currentSidePaddings);
+ }
+ }
+ super.setCurrentSidePaddings(currentSidePaddings);
+ }
+
public static class NotificationViewState extends ExpandableViewState {
private final StackScrollState mOverallState;
diff --git a/com/android/systemui/statusbar/ExpandableOutlineView.java b/com/android/systemui/statusbar/ExpandableOutlineView.java
index 25568907..b3d6e32d 100644
--- a/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -18,23 +18,58 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Canvas;
import android.graphics.Outline;
+import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
+
+import com.android.settingslib.Utils;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
* Like {@link ExpandableView}, but setting an outline for the height and clipping.
*/
public abstract class ExpandableOutlineView extends ExpandableView {
+ private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from(
+ "topRoundness",
+ ExpandableOutlineView::setTopRoundnessInternal,
+ ExpandableOutlineView::getCurrentTopRoundness,
+ R.id.top_roundess_animator_tag,
+ R.id.top_roundess_animator_end_tag,
+ R.id.top_roundess_animator_start_tag);
+ private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from(
+ "bottomRoundness",
+ ExpandableOutlineView::setBottomRoundnessInternal,
+ ExpandableOutlineView::getCurrentBottomRoundness,
+ R.id.bottom_roundess_animator_tag,
+ R.id.bottom_roundess_animator_end_tag,
+ R.id.bottom_roundess_animator_start_tag);
+ private static final AnimationProperties ROUNDNESS_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ private static final Path EMPTY_PATH = new Path();
+
private final Rect mOutlineRect = new Rect();
private boolean mCustomOutline;
private float mOutlineAlpha = -1f;
private float mOutlineRadius;
+ private boolean mAlwaysRoundBothCorners;
+ private Path mTmpPath = new Path();
+ private Path mTmpPath2 = new Path();
+ private float mCurrentBottomRoundness;
+ private float mCurrentTopRoundness;
+ private float mBottomRoundness;
+ private float mTopRoundness;
+ private int mBackgroundTop;
+ protected int mCurrentSidePaddings;
/**
* {@code true} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -45,61 +80,248 @@ public abstract class ExpandableOutlineView extends ExpandableView {
private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
- if (!mCustomOutline) {
- outline.setRoundRect(translation,
- mClipTopAmount,
- getWidth() + translation,
- Math.max(getActualHeight() - mClipBottomAmount, mClipTopAmount),
- mOutlineRadius);
- } else {
- outline.setRoundRect(mOutlineRect, mOutlineRadius);
+ Path clipPath = getClipPath();
+ if (clipPath != null && clipPath.isConvex()) {
+ // The path might not be convex in border cases where the view is small and clipped
+ outline.setConvexPath(clipPath);
}
outline.setAlpha(mOutlineAlpha);
}
};
+ private Path getClipPath() {
+ return getClipPath(false, /* ignoreTranslation */
+ false /* clipRoundedToBottom */);
+ }
+
+ protected Path getClipPath(boolean ignoreTranslation, boolean clipRoundedToBottom) {
+ int left;
+ int top;
+ int right;
+ int bottom;
+ int height;
+ Path intersectPath = null;
+ if (!mCustomOutline) {
+ int translation = mShouldTranslateContents && !ignoreTranslation
+ ? (int) getTranslation() : 0;
+ left = Math.max(translation + mCurrentSidePaddings, mCurrentSidePaddings);
+ top = mClipTopAmount + mBackgroundTop;
+ right = getWidth() - mCurrentSidePaddings + Math.min(translation, 0);
+ bottom = Math.max(getActualHeight(), top);
+ int intersectBottom = Math.max(getActualHeight() - mClipBottomAmount, top);
+ if (bottom != intersectBottom) {
+ if (clipRoundedToBottom) {
+ bottom = intersectBottom;
+ } else {
+ getRoundedRectPath(left, top, right,
+ intersectBottom, 0.0f,
+ 0.0f, mTmpPath2);
+ intersectPath = mTmpPath2;
+ }
+ }
+ } else {
+ left = mOutlineRect.left;
+ top = mOutlineRect.top;
+ right = mOutlineRect.right;
+ bottom = mOutlineRect.bottom;
+ left = Math.max(mCurrentSidePaddings, left);
+ right = Math.min(getWidth() - mCurrentSidePaddings, right);
+ }
+ height = bottom - top;
+ if (height == 0) {
+ return EMPTY_PATH;
+ }
+ float topRoundness = mAlwaysRoundBothCorners
+ ? mOutlineRadius : mCurrentTopRoundness * mOutlineRadius;
+ float bottomRoundness = mAlwaysRoundBothCorners
+ ? mOutlineRadius : mCurrentBottomRoundness * mOutlineRadius;
+ if (topRoundness + bottomRoundness > height) {
+ float overShoot = topRoundness + bottomRoundness - height;
+ topRoundness -= overShoot * mCurrentTopRoundness
+ / (mCurrentTopRoundness + mCurrentBottomRoundness);
+ bottomRoundness -= overShoot * mCurrentBottomRoundness
+ / (mCurrentTopRoundness + mCurrentBottomRoundness);
+ }
+ getRoundedRectPath(left, top, right, bottom, topRoundness,
+ bottomRoundness, mTmpPath);
+ Path roundedRectPath = mTmpPath;
+ if (intersectPath != null) {
+ roundedRectPath.op(intersectPath, Path.Op.INTERSECT);
+ }
+ return roundedRectPath;
+ }
+
+ protected Path getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
+ float bottomRoundness) {
+ getRoundedRectPath(left, top, right, bottom, topRoundness, bottomRoundness,
+ mTmpPath);
+ return mTmpPath;
+ }
+
+ private void getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
+ float bottomRoundness, Path outPath) {
+ outPath.reset();
+ int width = right - left;
+ float topRoundnessX = topRoundness;
+ float bottomRoundnessX = bottomRoundness;
+ topRoundnessX = Math.min(width / 2, topRoundnessX);
+ bottomRoundnessX = Math.min(width / 2, bottomRoundnessX);
+ if (topRoundness > 0.0f) {
+ outPath.moveTo(left, top + topRoundness);
+ outPath.quadTo(left, top, left + topRoundnessX, top);
+ outPath.lineTo(right - topRoundnessX, top);
+ outPath.quadTo(right, top, right, top + topRoundness);
+ } else {
+ outPath.moveTo(left, top);
+ outPath.lineTo(right, top);
+ }
+ if (bottomRoundness > 0.0f) {
+ outPath.lineTo(right, bottom - bottomRoundness);
+ outPath.quadTo(right, bottom, right - bottomRoundnessX, bottom);
+ outPath.lineTo(left + bottomRoundnessX, bottom);
+ outPath.quadTo(left, bottom, left, bottom - bottomRoundness);
+ } else {
+ outPath.lineTo(right, bottom);
+ outPath.lineTo(left, bottom);
+ }
+ outPath.close();
+ }
+
public ExpandableOutlineView(Context context, AttributeSet attrs) {
super(context, attrs);
setOutlineProvider(mProvider);
initDimens();
}
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ canvas.save();
+ if (childNeedsClipping(child)) {
+ Path clipPath = getCustomClipPath(child);
+ if (clipPath == null) {
+ clipPath = getClipPath();
+ }
+ if (clipPath != null) {
+ canvas.clipPath(clipPath);
+ }
+ }
+ boolean result = super.drawChild(canvas, child, drawingTime);
+ canvas.restore();
+ return result;
+ }
+
+ protected boolean childNeedsClipping(View child) {
+ return false;
+ }
+
+ protected boolean isClippingNeeded() {
+ return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ;
+
+ }
+
private void initDimens() {
Resources res = getResources();
mShouldTranslateContents =
res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe);
mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
- setClipToOutline(res.getBoolean(R.bool.config_clipNotificationsToOutline));
+ mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
+ if (!mAlwaysRoundBothCorners) {
+ mOutlineRadius = res.getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+ }
+ setClipToOutline(mAlwaysRoundBothCorners);
+ }
+
+ public void setTopRoundness(float topRoundness, boolean animate) {
+ if (mTopRoundness != topRoundness) {
+ mTopRoundness = topRoundness;
+ PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness,
+ ROUNDNESS_PROPERTIES, animate);
+ }
+ }
+
+ protected void applyRoundness() {
+ invalidateOutline();
+ invalidate();
+ }
+
+ public float getCurrentBackgroundRadiusTop() {
+ return mCurrentTopRoundness * mOutlineRadius;
+ }
+
+ public float getCurrentTopRoundness() {
+ return mCurrentTopRoundness;
+ }
+
+ public float getCurrentBottomRoundness() {
+ return mCurrentBottomRoundness;
+ }
+
+ protected float getCurrentBackgroundRadiusBottom() {
+ return mCurrentBottomRoundness * mOutlineRadius;
+ }
+
+ public void setBottomRoundness(float bottomRoundness, boolean animate) {
+ if (mBottomRoundness != bottomRoundness) {
+ mBottomRoundness = bottomRoundness;
+ PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness,
+ ROUNDNESS_PROPERTIES, animate);
+ }
+ }
+
+ protected void setBackgroundTop(int backgroundTop) {
+ if (mBackgroundTop != backgroundTop) {
+ mBackgroundTop = backgroundTop;
+ invalidateOutline();
+ }
+ }
+
+ private void setTopRoundnessInternal(float topRoundness) {
+ mCurrentTopRoundness = topRoundness;
+ applyRoundness();
+ }
+
+ private void setBottomRoundnessInternal(float bottomRoundness) {
+ mCurrentBottomRoundness = bottomRoundness;
+ applyRoundness();
}
public void onDensityOrFontScaleChanged() {
initDimens();
- invalidateOutline();
+ applyRoundness();
}
@Override
public void setActualHeight(int actualHeight, boolean notifyListeners) {
+ int previousHeight = getActualHeight();
super.setActualHeight(actualHeight, notifyListeners);
- invalidateOutline();
+ if (previousHeight != actualHeight) {
+ applyRoundness();
+ }
}
@Override
public void setClipTopAmount(int clipTopAmount) {
+ int previousAmount = getClipTopAmount();
super.setClipTopAmount(clipTopAmount);
- invalidateOutline();
+ if (previousAmount != clipTopAmount) {
+ applyRoundness();
+ }
}
@Override
public void setClipBottomAmount(int clipBottomAmount) {
+ int previousAmount = getClipBottomAmount();
super.setClipBottomAmount(clipBottomAmount);
- invalidateOutline();
+ if (previousAmount != clipBottomAmount) {
+ applyRoundness();
+ }
}
protected void setOutlineAlpha(float alpha) {
if (alpha != mOutlineAlpha) {
mOutlineAlpha = alpha;
- invalidateOutline();
+ applyRoundness();
}
}
@@ -113,8 +335,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
} else {
mCustomOutline = false;
- setClipToOutline(false);
- invalidateOutline();
+ applyRoundness();
}
}
@@ -151,15 +372,22 @@ public abstract class ExpandableOutlineView extends ExpandableView {
protected void setOutlineRect(float left, float top, float right, float bottom) {
mCustomOutline = true;
- setClipToOutline(true);
mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom);
// Outlines need to be at least 1 dp
mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
+ applyRoundness();
+ }
- invalidateOutline();
+ public Path getCustomClipPath(View child) {
+ return null;
}
+ public void setCurrentSidePaddings(float currentSidePaddings) {
+ mCurrentSidePaddings = (int) currentSidePaddings;
+ invalidateOutline();
+ invalidate();
+ }
}
diff --git a/com/android/systemui/statusbar/ExpandableView.java b/com/android/systemui/statusbar/ExpandableView.java
index aac9af8a..18b98602 100644
--- a/com/android/systemui/statusbar/ExpandableView.java
+++ b/com/android/systemui/statusbar/ExpandableView.java
@@ -202,6 +202,10 @@ public abstract class ExpandableView extends FrameLayout {
return mDark;
}
+ public boolean isRemoved() {
+ return false;
+ }
+
/**
* See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
* the upcoming state of hiding sensitive notifications. It gets called at the very beginning
diff --git a/com/android/systemui/statusbar/NotificationBackgroundView.java b/com/android/systemui/statusbar/NotificationBackgroundView.java
index 81a99bc1..68cf51c0 100644
--- a/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -19,37 +19,57 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
-import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;
+import com.android.systemui.R;
+
/**
* A view that can be used for both the dimmed and normal background of an notification.
*/
public class NotificationBackgroundView extends View {
+ private final boolean mDontModifyCorners;
private Drawable mBackground;
private int mClipTopAmount;
private int mActualHeight;
private int mClipBottomAmount;
private int mTintColor;
+ private float[] mCornerRadii = new float[8];
+ private int mCurrentSidePaddings;
+ private boolean mBottomIsRounded;
+ private int mBackgroundTop;
+ private boolean mBottomAmountClips = true;
public NotificationBackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mDontModifyCorners = getResources().getBoolean(
+ R.bool.config_clipNotificationsToOutline);
}
@Override
protected void onDraw(Canvas canvas) {
- draw(canvas, mBackground);
+ if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop) {
+ canvas.save();
+ canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+ draw(canvas, mBackground);
+ canvas.restore();
+ }
}
private void draw(Canvas canvas, Drawable drawable) {
- int bottom = mActualHeight - mClipBottomAmount;
- if (drawable != null && bottom > mClipTopAmount) {
- drawable.setBounds(0, mClipTopAmount, getWidth(), bottom);
+ if (drawable != null) {
+ int bottom = mActualHeight;
+ if (mBottomIsRounded && mBottomAmountClips) {
+ bottom -= mClipBottomAmount;
+ }
+ drawable.setBounds(mCurrentSidePaddings, mBackgroundTop,
+ getWidth() - mCurrentSidePaddings, bottom);
drawable.draw(canvas);
}
}
@@ -87,6 +107,7 @@ public class NotificationBackgroundView extends View {
unscheduleDrawable(mBackground);
}
mBackground = background;
+ mBackground.mutate();
if (mBackground != null) {
mBackground.setCallback(this);
setTint(mTintColor);
@@ -94,6 +115,7 @@ public class NotificationBackgroundView extends View {
if (mBackground instanceof RippleDrawable) {
((RippleDrawable) mBackground).setForceSoftware(true);
}
+ updateBackgroundRadii();
invalidate();
}
@@ -152,4 +174,45 @@ public class NotificationBackgroundView extends View {
public void setDrawableAlpha(int drawableAlpha) {
mBackground.setAlpha(drawableAlpha);
}
+
+ public void setRoundness(float topRoundness, float bottomRoundNess) {
+ mBottomIsRounded = bottomRoundNess != 0.0f;
+ mCornerRadii[0] = topRoundness;
+ mCornerRadii[1] = topRoundness;
+ mCornerRadii[2] = topRoundness;
+ mCornerRadii[3] = topRoundness;
+ mCornerRadii[4] = bottomRoundNess;
+ mCornerRadii[5] = bottomRoundNess;
+ mCornerRadii[6] = bottomRoundNess;
+ mCornerRadii[7] = bottomRoundNess;
+ updateBackgroundRadii();
+ }
+
+ public void setBottomAmountClips(boolean clips) {
+ if (clips != mBottomAmountClips) {
+ mBottomAmountClips = clips;
+ invalidate();
+ }
+ }
+
+ private void updateBackgroundRadii() {
+ if (mDontModifyCorners) {
+ return;
+ }
+ if (mBackground instanceof LayerDrawable) {
+ GradientDrawable gradientDrawable =
+ (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
+ gradientDrawable.setCornerRadii(mCornerRadii);
+ }
+ }
+
+ public void setCurrentSidePaddings(float currentSidePaddings) {
+ mCurrentSidePaddings = (int) currentSidePaddings;
+ invalidate();
+ }
+
+ public void setBackgroundTop(int backgroundTop) {
+ mBackgroundTop = backgroundTop;
+ invalidate();
+ }
}
diff --git a/com/android/systemui/statusbar/NotificationContentView.java b/com/android/systemui/statusbar/NotificationContentView.java
index 9e059c89..39c21313 100644
--- a/com/android/systemui/statusbar/NotificationContentView.java
+++ b/com/android/systemui/statusbar/NotificationContentView.java
@@ -24,6 +24,7 @@ import android.graphics.Rect;
import android.os.Build;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
@@ -34,8 +35,8 @@ import android.widget.ImageView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.HybridGroupManager;
+import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
@@ -49,6 +50,7 @@ import com.android.systemui.statusbar.policy.RemoteInputView;
*/
public class NotificationContentView extends FrameLayout {
+ private static final String TAG = "NotificationContentView";
public static final int VISIBLE_TYPE_CONTRACTED = 0;
public static final int VISIBLE_TYPE_EXPANDED = 1;
public static final int VISIBLE_TYPE_HEADSUP = 2;
@@ -58,9 +60,9 @@ public class NotificationContentView extends FrameLayout {
public static final int UNDEFINED = -1;
private final Rect mClipBounds = new Rect();
- private final int mMinContractedHeight;
- private final int mNotificationContentMarginEnd;
+ private int mMinContractedHeight;
+ private int mNotificationContentMarginEnd;
private View mContractedChild;
private View mExpandedChild;
private View mHeadsUpChild;
@@ -134,15 +136,22 @@ public class NotificationContentView extends FrameLayout {
private int mClipBottomAmount;
private boolean mIsLowPriority;
private boolean mIsContentExpandable;
+ private int mCustomViewSidePaddings;
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mHybridGroupManager = new HybridGroupManager(getContext(), this);
+ initView();
+ }
+
+ public void initView() {
mMinContractedHeight = getResources().getDimensionPixelSize(
R.dimen.min_notification_layout_height);
mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_end);
+ mCustomViewSidePaddings = getResources().getDimensionPixelSize(
+ R.dimen.notification_content_custom_view_side_padding);
}
public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight,
@@ -178,7 +187,7 @@ public class NotificationContentView extends FrameLayout {
: MeasureSpec.makeMeasureSpec(size, useExactly
? MeasureSpec.EXACTLY
: MeasureSpec.AT_MOST);
- mExpandedChild.measure(widthMeasureSpec, spec);
+ measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0);
maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
}
if (mContractedChild != null) {
@@ -196,22 +205,22 @@ public class NotificationContentView extends FrameLayout {
} else {
heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
}
- mContractedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
int measuredHeight = mContractedChild.getMeasuredHeight();
if (measuredHeight < mMinContractedHeight) {
heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY);
- mContractedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
}
maxChildHeight = Math.max(maxChildHeight, measuredHeight);
if (updateContractedHeaderWidth()) {
- mContractedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
}
if (mExpandedChild != null
&& mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) {
// the Expanded child is smaller then the collapsed. Let's remeasure it.
heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(),
MeasureSpec.EXACTLY);
- mExpandedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, heightSpec, 0);
}
}
if (mHeadsUpChild != null) {
@@ -223,9 +232,9 @@ public class NotificationContentView extends FrameLayout {
size = Math.min(size, layoutParams.height);
useExactly = true;
}
- mHeadsUpChild.measure(widthMeasureSpec,
+ measureChildWithMargins(mHeadsUpChild, widthMeasureSpec, 0,
MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
- : MeasureSpec.AT_MOST));
+ : MeasureSpec.AT_MOST), 0);
maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
}
if (mSingleLineView != null) {
@@ -382,6 +391,38 @@ public class NotificationContentView extends FrameLayout {
mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+ updateMargins(child);
+ }
+
+ private void updateMargins(View child) {
+ if (child == null) {
+ return;
+ }
+ NotificationViewWrapper wrapper = getWrapperForView(child);
+ boolean isCustomView = wrapper instanceof NotificationCustomViewWrapper;
+ boolean needsMargins = isCustomView &&
+ child.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P;
+ int padding = needsMargins ? mCustomViewSidePaddings : 0;
+ MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
+ layoutParams.setMarginStart(padding);
+ layoutParams.setMarginEnd(padding);
+ child.setLayoutParams(layoutParams);
+ }
+
+ private NotificationViewWrapper getWrapperForView(View child) {
+ if (child == mContractedChild) {
+ return mContractedWrapper;
+ }
+ if (child == mExpandedChild) {
+ return mExpandedWrapper;
+ }
+ if (child == mHeadsUpChild) {
+ return mHeadsUpWrapper;
+ }
+ if (child == mAmbientChild) {
+ return mAmbientWrapper;
+ }
+ return null;
}
public void setExpandedChild(View child) {
@@ -415,6 +456,7 @@ public class NotificationContentView extends FrameLayout {
mExpandedChild = child;
mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
+ updateMargins(child);
}
public void setHeadsUpChild(View child) {
@@ -448,6 +490,7 @@ public class NotificationContentView extends FrameLayout {
mHeadsUpChild = child;
mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
+ updateMargins(child);
}
public void setAmbientChild(View child) {
@@ -643,6 +686,13 @@ public class NotificationContentView extends FrameLayout {
int endHeight = getViewForVisibleType(mVisibleType).getHeight();
int progress = Math.abs(mContentHeight - startHeight);
int totalDistance = Math.abs(endHeight - startHeight);
+ if (totalDistance == 0) {
+ Log.wtf(TAG, "the total transformation distance is 0"
+ + "\n StartType: " + mTransformationStartVisibleType + " height: " + startHeight
+ + "\n VisibleType: " + mVisibleType + " height: " + endHeight
+ + "\n mContentHeight: " + mContentHeight);
+ return 1.0f;
+ }
float amount = (float) progress / (float) totalDistance;
return Math.min(1.0f, amount);
}
@@ -1459,4 +1509,20 @@ public class NotificationContentView extends FrameLayout {
}
return false;
}
+
+ public boolean shouldClipToSidePaddings() {
+ boolean needsPaddings = shouldClipToSidePaddings(getVisibleType());
+ if (mUserExpanding) {
+ needsPaddings |= shouldClipToSidePaddings(mTransformationStartVisibleType);
+ }
+ return needsPaddings;
+ }
+
+ private boolean shouldClipToSidePaddings(int visibleType) {
+ NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
+ if (visibleWrapper == null) {
+ return false;
+ }
+ return visibleWrapper.shouldClipToSidePaddings();
+ }
}
diff --git a/com/android/systemui/statusbar/NotificationGutsManager.java b/com/android/systemui/statusbar/NotificationGutsManager.java
index b585bdff..f451fda6 100644
--- a/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -37,6 +37,7 @@ import android.view.accessibility.AccessibilityManager;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.Interpolators;
@@ -75,17 +76,20 @@ public class NotificationGutsManager implements Dumpable {
private NotificationGuts mNotificationGutsExposed;
private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
private final NotificationInfo.CheckSaveListener mCheckSaveListener;
+ private final OnSettingsClickListener mOnSettingsClickListener;
private String mKeyToRemoveOnGutsClosed;
public NotificationGutsManager(
NotificationPresenter presenter,
NotificationStackScrollLayout stackScroller,
NotificationInfo.CheckSaveListener checkSaveListener,
- Context context) {
+ Context context,
+ OnSettingsClickListener onSettingsClickListener) {
mPresenter = presenter;
mStackScroller = stackScroller;
mCheckSaveListener = checkSaveListener;
mContext = context;
+ mOnSettingsClickListener = onSettingsClickListener;
Resources res = context.getResources();
mNonBlockablePkgs = new HashSet<>();
@@ -189,6 +193,7 @@ public class NotificationGutsManager implements Dumpable {
onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
guts.resetFalsingCheck();
+ mOnSettingsClickListener.onClick(sbn.getKey());
startAppNotificationSettingsActivity(pkg, appUid, channel);
};
}
@@ -352,4 +357,8 @@ public class NotificationGutsManager implements Dumpable {
pw.print("mKeyToRemoveOnGutsClosed: ");
pw.println(mKeyToRemoveOnGutsClosed);
}
+
+ public interface OnSettingsClickListener {
+ void onClick(String key);
+ }
}
diff --git a/com/android/systemui/statusbar/NotificationMenuRow.java b/com/android/systemui/statusbar/NotificationMenuRow.java
index 99b4b079..b2604fe0 100644
--- a/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -88,6 +88,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
private float mHorizSpaceForIcon = -1;
private int mVertSpaceForIcons = -1;
private int mIconPadding = -1;
+ private int mSidePadding;
private float mAlpha = 0f;
private float mPrevX;
@@ -175,6 +176,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
final Resources res = mContext.getResources();
mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
+ mSidePadding = res.getDimensionPixelSize(R.dimen.notification_lockscreen_side_paddings);
mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
mMenuItems.clear();
// Construct the menu items based on the notification
@@ -496,8 +498,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
final int count = mMenuContainer.getChildCount();
for (int i = 0; i < count; i++) {
final View v = mMenuContainer.getChildAt(i);
- final float left = i * mHorizSpaceForIcon;
- final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1));
+ final float left = mSidePadding + i * mHorizSpaceForIcon;
+ final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1)) - mSidePadding;
v.setX(showOnLeft ? left : right);
}
mOnLeft = showOnLeft;
diff --git a/com/android/systemui/statusbar/NotificationShelf.java b/com/android/systemui/statusbar/NotificationShelf.java
index 5557dde7..b7a00ebc 100644
--- a/com/android/systemui/statusbar/NotificationShelf.java
+++ b/com/android/systemui/statusbar/NotificationShelf.java
@@ -85,6 +85,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
private boolean mVibrationOnAnimation;
private boolean mUserTouchingScreen;
private boolean mTouchActive;
+ private float mFirstElementRoundness;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -107,6 +108,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
NotificationPanelView.DOZE_ANIMATION_DURATION);
mShelfState = new ShelfState();
+ setBottomRoundness(1.0f, false /* animate */);
initDimens();
}
@@ -252,6 +254,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
boolean expandingAnimated = mAmbientState.isExpansionChanging()
&& !mAmbientState.isPanelTracking();
int baseZHeight = mAmbientState.getBaseZHeight();
+ int backgroundTop = 0;
+ float firstElementRoundness = 0.0f;
while (notificationIndex < mHostLayout.getChildCount()) {
ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
notificationIndex++;
@@ -302,9 +306,20 @@ public class NotificationShelf extends ActivatableNotificationView implements
if (notGoneIndex != 0 || !aboveShelf) {
row.setAboveShelf(false);
}
+ if (notGoneIndex == 0) {
+ StatusBarIconView icon = row.getEntry().expandedIcon;
+ NotificationIconContainer.IconState iconState = getIconState(icon);
+ if (iconState.clampedAppearAmount == 1.0f) {
+ // only if the first icon is fully in the shelf we want to clip to it!
+ backgroundTop = (int) (row.getTranslationY() - getTranslationY());
+ firstElementRoundness = row.getCurrentTopRoundness();
+ }
+ }
notGoneIndex++;
previousColor = ownColorUntinted;
}
+ setBackgroundTop(backgroundTop);
+ setFirstElementRoundness(firstElementRoundness);
mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
mShelfIcons.calculateIconTranslations();
mShelfIcons.applyIconStates();
@@ -325,6 +340,13 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
}
+ private void setFirstElementRoundness(float firstElementRoundness) {
+ if (mFirstElementRoundness != firstElementRoundness) {
+ mFirstElementRoundness = firstElementRoundness;
+ setTopRoundness(firstElementRoundness, false /* animate */);
+ }
+ }
+
private void updateIconClipAmount(ExpandableNotificationRow row) {
float maxTop = row.getTranslationY();
StatusBarIconView icon = row.getEntry().expandedIcon;
diff --git a/com/android/systemui/statusbar/RemoteInputController.java b/com/android/systemui/statusbar/RemoteInputController.java
index 7f28c4c2..ff6c775c 100644
--- a/com/android/systemui/statusbar/RemoteInputController.java
+++ b/com/android/systemui/statusbar/RemoteInputController.java
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar;
import com.android.internal.util.Preconditions;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.phone.StatusBarWindowManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.RemoteInputView;
import android.util.ArrayMap;
@@ -38,11 +37,11 @@ public class RemoteInputController {
= new ArrayList<>();
private final ArrayMap<String, Object> mSpinning = new ArrayMap<>();
private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
- private final HeadsUpManager mHeadsUpManager;
+ private final Delegate mDelegate;
- public RemoteInputController(HeadsUpManager headsUpManager) {
+ public RemoteInputController(Delegate delegate) {
addCallback(Dependency.get(StatusBarWindowManager.class));
- mHeadsUpManager = headsUpManager;
+ mDelegate = delegate;
}
/**
@@ -114,7 +113,7 @@ public class RemoteInputController {
}
private void apply(NotificationData.Entry entry) {
- mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry));
+ mDelegate.setRemoteInputActive(entry, isRemoteInputActive(entry));
boolean remoteInputActive = isRemoteInputActive();
int N = mCallbacks.size();
for (int i = 0; i < N; i++) {
@@ -204,9 +203,35 @@ public class RemoteInputController {
}
}
+ public void requestDisallowLongPressAndDismiss() {
+ mDelegate.requestDisallowLongPressAndDismiss();
+ }
+
+ public void lockScrollTo(NotificationData.Entry entry) {
+ mDelegate.lockScrollTo(entry);
+ }
+
public interface Callback {
default void onRemoteInputActive(boolean active) {}
default void onRemoteInputSent(NotificationData.Entry entry) {}
}
+
+ public interface Delegate {
+ /**
+ * Activate remote input if necessary.
+ */
+ void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive);
+
+ /**
+ * Request that the view does not dismiss nor perform long press for the current touch.
+ */
+ void requestDisallowLongPressAndDismiss();
+
+ /**
+ * Request that the view is made visible by scrolling to it, and keep the scroll locked until
+ * the user scrolls, or {@param v} loses focus or is detached.
+ */
+ void lockScrollTo(NotificationData.Entry entry);
+ }
}
diff --git a/com/android/systemui/statusbar/ScrimView.java b/com/android/systemui/statusbar/ScrimView.java
index a53e348f..88303522 100644
--- a/com/android/systemui/statusbar/ScrimView.java
+++ b/com/android/systemui/statusbar/ScrimView.java
@@ -41,6 +41,7 @@ import android.view.animation.Interpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.drawable.GradientDrawable;
+import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -50,6 +51,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
public class ScrimView extends View implements ConfigurationController.ConfigurationListener {
private static final String TAG = "ScrimView";
private final ColorExtractor.GradientColors mColors;
+ private int mDensity;
private boolean mDrawAsSrc;
private float mViewAlpha = 1.0f;
private ValueAnimator mAlphaAnimator;
@@ -72,6 +74,7 @@ public class ScrimView extends View implements ConfigurationController.Configura
}
};
private Runnable mChangeRunnable;
+ private int mCornerRadius;
public ScrimView(Context context) {
this(context, null);
@@ -93,6 +96,24 @@ public class ScrimView extends View implements ConfigurationController.Configura
mColors = new ColorExtractor.GradientColors();
updateScreenSize();
updateColorWithTint(false);
+ initView();
+ final Configuration currentConfig = mContext.getResources().getConfiguration();
+ mDensity = currentConfig.densityDpi;
+ }
+
+ private void initView() {
+ mCornerRadius = getResources().getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ int densityDpi = newConfig.densityDpi;
+ if (mDensity != densityDpi) {
+ mDensity = densityDpi;
+ initView();
+ }
}
@Override
@@ -145,6 +166,28 @@ public class ScrimView extends View implements ConfigurationController.Configura
mDrawable.draw(canvas);
canvas.restore();
}
+ // We also need to draw the rounded corners of the background
+ canvas.save();
+ canvas.clipRect(mExcludedRect.left, mExcludedRect.top,
+ mExcludedRect.left + mCornerRadius, mExcludedRect.top + mCornerRadius);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ canvas.save();
+ canvas.clipRect(mExcludedRect.right - mCornerRadius, mExcludedRect.top,
+ mExcludedRect.right, mExcludedRect.top + mCornerRadius);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ canvas.save();
+ canvas.clipRect(mExcludedRect.left, mExcludedRect.bottom - mCornerRadius,
+ mExcludedRect.left + mCornerRadius, mExcludedRect.bottom);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ canvas.save();
+ canvas.clipRect(mExcludedRect.right - mCornerRadius,
+ mExcludedRect.bottom - mCornerRadius,
+ mExcludedRect.right, mExcludedRect.bottom);
+ mDrawable.draw(canvas);
+ canvas.restore();
}
}
}
@@ -252,6 +295,13 @@ public class ScrimView extends View implements ConfigurationController.Configura
return false;
}
+ /**
+ * It might look counterintuitive to have another method to set the alpha instead of
+ * only using {@link #setAlpha(float)}. In this case we're in a hardware layer
+ * optimizing blend modes, so it makes sense.
+ *
+ * @param alpha Gradient alpha from 0 to 1.
+ */
public void setViewAlpha(float alpha) {
if (alpha != mViewAlpha) {
mViewAlpha = alpha;
diff --git a/com/android/systemui/statusbar/car/CarNavigationBarController.java b/com/android/systemui/statusbar/car/CarNavigationBarController.java
index f5c77f26..64c52ed6 100644
--- a/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -369,7 +369,7 @@ class CarNavigationBarController {
private void onFacetClicked(Intent intent, int index) {
String packageName = intent.getPackage();
- if (packageName == null) {
+ if (packageName == null && !intent.getCategories().contains(Intent.CATEGORY_HOME)) {
return;
}
diff --git a/com/android/systemui/statusbar/notification/AnimatableProperty.java b/com/android/systemui/statusbar/notification/AnimatableProperty.java
new file mode 100644
index 00000000..d7b211f9
--- /dev/null
+++ b/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.FloatProperty;
+import android.util.Property;
+import android.view.View;
+
+import com.android.systemui.statusbar.stack.AnimationProperties;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * An animatable property of a view. Used with {@link PropertyAnimator}
+ */
+public interface AnimatableProperty {
+ int getAnimationStartTag();
+
+ int getAnimationEndTag();
+
+ int getAnimatorTag();
+
+ Property getProperty();
+
+ static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter,
+ Function<T, Float> getter, int animatorTag, int startValueTag, int endValueTag) {
+ Property<T, Float> property = new FloatProperty<T>(name) {
+
+ @Override
+ public Float get(T object) {
+ return getter.apply(object);
+ }
+
+ @Override
+ public void setValue(T object, float value) {
+ setter.accept(object, value);
+ }
+ };
+ return new AnimatableProperty() {
+ @Override
+ public int getAnimationStartTag() {
+ return startValueTag;
+ }
+
+ @Override
+ public int getAnimationEndTag() {
+ return endValueTag;
+ }
+
+ @Override
+ public int getAnimatorTag() {
+ return animatorTag;
+ }
+
+ @Override
+ public Property getProperty() {
+ return property;
+ }
+ };
+ }
+}
diff --git a/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index fc420eb7..27defcac 100644
--- a/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -89,6 +89,7 @@ public class MessagingLayoutTransformState extends TransformState {
private void transformViewInternal(MessagingLayoutTransformState mlt,
float transformationAmount, boolean to) {
+ ensureVisible();
ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
mMessagingLayout.getMessagingGroups());
ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
@@ -332,6 +333,7 @@ public class MessagingLayoutTransformState extends TransformState {
@Override
public void setVisible(boolean visible, boolean force) {
+ super.setVisible(visible, force);
resetTransformedView();
ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
for (int i = 0; i < ownGroups.size(); i++) {
diff --git a/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index bca4b43a..66682e4c 100644
--- a/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
+import android.os.Build;
import android.view.View;
import com.android.systemui.R;
@@ -37,6 +38,7 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper {
private final Paint mGreyPaint = new Paint();
private boolean mIsLegacy;
private int mLegacyColor;
+ private boolean mBeforeP;
protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row);
@@ -115,4 +117,17 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper {
super.setLegacy(legacy);
mIsLegacy = legacy;
}
+
+ @Override
+ public boolean shouldClipToSidePaddings() {
+ // Before P we ensure that they are now drawing inside out content bounds since we inset
+ // the view. If they target P, then we don't have that guarantee and we need to be safe.
+ return !mBeforeP;
+ }
+
+ @Override
+ public void onContentUpdated(ExpandableNotificationRow row) {
+ super.onContentUpdated(row);
+ mBeforeP = row.getEntry().targetSdk < Build.VERSION_CODES.P;
+ }
}
diff --git a/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index eb211a10..060e6d65 100644
--- a/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -58,6 +58,11 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi
@Override
public boolean isDimmable() {
- return false;
+ return getCustomBackgroundColor() == 0;
+ }
+
+ @Override
+ public boolean shouldClipToSidePaddings() {
+ return true;
}
}
diff --git a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index fd085d9c..e07112f9 100644
--- a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -265,6 +265,11 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
updateActionOffset();
}
+ @Override
+ public boolean shouldClipToSidePaddings() {
+ return mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE;
+ }
+
private void updateActionOffset() {
if (mActionsContainer != null) {
// We should never push the actions higher than they are in the headsup view.
diff --git a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 1cd5f15b..8a767bb7 100644
--- a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -194,4 +194,8 @@ public abstract class NotificationViewWrapper implements TransformableView {
public int getMinLayoutHeight() {
return 0;
}
+
+ public boolean shouldClipToSidePaddings() {
+ return false;
+ }
}
diff --git a/com/android/systemui/statusbar/notification/PropertyAnimator.java b/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 80ba9439..92dcc9e3 100644
--- a/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -34,6 +34,19 @@ import com.android.systemui.statusbar.stack.ViewState;
*/
public class PropertyAnimator {
+ public static <T extends View> void setProperty(final T view,
+ AnimatableProperty animatableProperty, float newEndValue,
+ AnimationProperties properties, boolean animated) {
+ int animatorTag = animatableProperty.getAnimatorTag();
+ ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag);
+ if (previousAnimator != null || animated) {
+ startAnimation(view, animatableProperty, newEndValue, properties);
+ } else {
+ // no new animation needed, let's just apply the value
+ animatableProperty.getProperty().set(view, newEndValue);
+ }
+ }
+
public static <T extends View> void startAnimation(final T view,
AnimatableProperty animatableProperty, float newEndValue,
AnimationProperties properties) {
@@ -102,10 +115,4 @@ public class PropertyAnimator {
view.setTag(animationEndTag, newEndValue);
}
- public interface AnimatableProperty {
- int getAnimationStartTag();
- int getAnimationEndTag();
- int getAnimatorTag();
- Property getProperty();
- }
}
diff --git a/com/android/systemui/statusbar/notification/TransformState.java b/com/android/systemui/statusbar/notification/TransformState.java
index ad07af0a..dec5303f 100644
--- a/com/android/systemui/statusbar/notification/TransformState.java
+++ b/com/android/systemui/statusbar/notification/TransformState.java
@@ -95,18 +95,22 @@ public class TransformState {
public void transformViewFrom(TransformState otherState, float transformationAmount) {
mTransformedView.animate().cancel();
if (sameAs(otherState)) {
- if (mTransformedView.getVisibility() == View.INVISIBLE
- || mTransformedView.getAlpha() != 1.0f) {
- // We have the same content, lets show ourselves
- mTransformedView.setAlpha(1.0f);
- mTransformedView.setVisibility(View.VISIBLE);
- }
+ ensureVisible();
} else {
CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
}
transformViewFullyFrom(otherState, transformationAmount);
}
+ protected void ensureVisible() {
+ if (mTransformedView.getVisibility() == View.INVISIBLE
+ || mTransformedView.getAlpha() != 1.0f) {
+ // We have the same content, lets show ourselves
+ mTransformedView.setAlpha(1.0f);
+ mTransformedView.setVisibility(View.VISIBLE);
+ }
+ }
+
public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount);
}
diff --git a/com/android/systemui/statusbar/phone/DozeParameters.java b/com/android/systemui/statusbar/phone/DozeParameters.java
index 6b7397b3..3f57c2f9 100644
--- a/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -46,10 +46,8 @@ public class DozeParameters {
public void dump(PrintWriter pw) {
pw.println(" DozeParameters:");
pw.print(" getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
- pw.print(" getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false));
- pw.print(" getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true));
- pw.print(" getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false));
- pw.print(" getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true));
+ pw.print(" getPulseDuration(): "); pw.println(getPulseDuration());
+ pw.print(" getPulseInDuration(): "); pw.println(getPulseInDuration());
pw.print(" getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
pw.print(" getPulseOutDuration(): "); pw.println(getPulseOutDuration());
pw.print(" getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
@@ -81,14 +79,12 @@ public class DozeParameters {
return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
}
- public int getPulseDuration(boolean pickup) {
- return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
+ public int getPulseDuration() {
+ return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
}
- public int getPulseInDuration(boolean pickupOrDoubleTap) {
- return pickupOrDoubleTap
- ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup)
- : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
+ public int getPulseInDuration() {
+ return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
}
public int getPulseVisibleDuration() {
diff --git a/com/android/systemui/statusbar/phone/DozeScrimController.java b/com/android/systemui/statusbar/phone/DozeScrimController.java
index 8afb8490..1011383b 100644
--- a/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -16,16 +16,11 @@
package com.android.systemui.statusbar.phone;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
-import android.view.animation.Interpolator;
-import com.android.systemui.Interpolators;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
@@ -40,74 +35,59 @@ public class DozeScrimController {
private final Handler mHandler = new Handler();
private final ScrimController mScrimController;
- private final Context mContext;
-
private boolean mDozing;
private DozeHost.PulseCallback mPulseCallback;
private int mPulseReason;
- private Animator mInFrontAnimator;
- private Animator mBehindAnimator;
- private float mInFrontTarget;
- private float mBehindTarget;
- private boolean mDozingAborted;
- private boolean mWakeAndUnlocking;
private boolean mFullyPulsing;
- private float mAodFrontScrimOpacity = 0;
- private Runnable mSetDozeInFrontAlphaDelayed;
+ private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
+ @Override
+ public void onDisplayBlanked() {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
+ + DozeLog.pulseReasonToString(mPulseReason));
+ }
+ if (!mDozing) {
+ return;
+ }
+
+ // Signal that the pulse is ready to turn the screen on and draw.
+ pulseStarted();
+ }
+
+ @Override
+ public void onFinished() {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
+ }
+ if (!mDozing) {
+ return;
+ }
+ mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
+ mHandler.postDelayed(mPulseOutExtended,
+ mDozeParameters.getPulseVisibleDurationExtended());
+ mFullyPulsing = true;
+ }
+
+ /**
+ * Transition was aborted before it was over.
+ */
+ @Override
+ public void onCancelled() {
+ pulseFinished();
+ }
+ };
public DozeScrimController(ScrimController scrimController, Context context) {
- mContext = context;
mScrimController = scrimController;
mDozeParameters = new DozeParameters(context);
}
- public void setDozing(boolean dozing, boolean animate) {
+ public void setDozing(boolean dozing) {
if (mDozing == dozing) return;
mDozing = dozing;
- mWakeAndUnlocking = false;
- if (mDozing) {
- mDozingAborted = false;
- abortAnimations();
- mScrimController.setDozeBehindAlpha(1f);
- setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f);
- } else {
+ if (!mDozing) {
cancelPulsing();
- if (animate) {
- startScrimAnimation(false /* inFront */, 0f /* target */,
- NotificationPanelView.DOZE_ANIMATION_DURATION,
- Interpolators.LINEAR_OUT_SLOW_IN);
- startScrimAnimation(true /* inFront */, 0f /* target */,
- NotificationPanelView.DOZE_ANIMATION_DURATION,
- Interpolators.LINEAR_OUT_SLOW_IN);
- } else {
- abortAnimations();
- mScrimController.setDozeBehindAlpha(0f);
- setDozeInFrontAlpha(0f);
- }
- }
- }
-
- /**
- * Set the opacity of the front scrim when showing AOD1
- *
- * Used to emulate lower brightness values than the hardware supports natively.
- */
- public void setAodDimmingScrim(float scrimOpacity) {
- mAodFrontScrimOpacity = scrimOpacity;
- if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking
- && mDozeParameters.getAlwaysOn()) {
- setDozeInFrontAlpha(mAodFrontScrimOpacity);
- }
- }
-
- public void setWakeAndUnlocking() {
- // Immediately abort the doze scrims in case of wake-and-unlock
- // for pulsing so the Keyguard fade-out animation scrim can take over.
- if (!mWakeAndUnlocking) {
- mWakeAndUnlocking = true;
- mScrimController.setDozeBehindAlpha(0f);
- setDozeInFrontAlpha(0f);
}
}
@@ -118,37 +98,21 @@ public class DozeScrimController {
}
if (!mDozing || mPulseCallback != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
+ + (mPulseCallback != null));
+ }
// Pulse suppressed.
callback.onPulseFinished();
return;
}
- // Begin pulse. Note that it's very important that the pulse finished callback
+ // Begin pulse. Note that it's very important that the pulse finished callback
// be invoked when we're done so that the caller can drop the pulse wakelock.
mPulseCallback = callback;
mPulseReason = reason;
- setDozeInFrontAlpha(1f);
- mHandler.post(mPulseIn);
- }
-
- /**
- * Aborts pulsing immediately.
- */
- public void abortPulsing() {
- cancelPulsing();
- if (mDozing && !mWakeAndUnlocking) {
- mScrimController.setDozeBehindAlpha(1f);
- setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() && !mDozingAborted
- ? mAodFrontScrimOpacity : 1f);
- }
- }
- /**
- * Aborts dozing immediately.
- */
- public void abortDoze() {
- mDozingAborted = true;
- abortPulsing();
+ mScrimController.transitionTo(ScrimState.PULSING, mScrimCallback);
}
public void pulseOutNow() {
@@ -157,17 +121,6 @@ public class DozeScrimController {
}
}
- public void onScreenTurnedOn() {
- if (isPulsing()) {
- final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP
- || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
- startScrimAnimation(true /* inFront */, 0f,
- mDozeParameters.getPulseInDuration(pickupOrDoubleTap),
- pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT,
- mPulseInFinished);
- }
- }
-
public boolean isPulsing() {
return mPulseCallback != null;
}
@@ -181,11 +134,9 @@ public class DozeScrimController {
}
private void cancelPulsing() {
- if (DEBUG) Log.d(TAG, "Cancel pulsing");
-
if (mPulseCallback != null) {
+ if (DEBUG) Log.d(TAG, "Cancel pulsing");
mFullyPulsing = false;
- mHandler.removeCallbacks(mPulseIn);
mHandler.removeCallbacks(mPulseOut);
mHandler.removeCallbacks(mPulseOutExtended);
pulseFinished();
@@ -193,151 +144,20 @@ public class DozeScrimController {
}
private void pulseStarted() {
+ DozeLog.tracePulseStart(mPulseReason);
if (mPulseCallback != null) {
mPulseCallback.onPulseStarted();
}
}
private void pulseFinished() {
+ DozeLog.tracePulseFinish();
if (mPulseCallback != null) {
mPulseCallback.onPulseFinished();
mPulseCallback = null;
}
}
- private void abortAnimations() {
- if (mInFrontAnimator != null) {
- mInFrontAnimator.cancel();
- }
- if (mBehindAnimator != null) {
- mBehindAnimator.cancel();
- }
- }
-
- private void startScrimAnimation(final boolean inFront, float target, long duration,
- Interpolator interpolator) {
- startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */);
- }
-
- private void startScrimAnimation(final boolean inFront, float target, long duration,
- Interpolator interpolator, final Runnable endRunnable) {
- Animator current = getCurrentAnimator(inFront);
- if (current != null) {
- float currentTarget = getCurrentTarget(inFront);
- if (currentTarget == target) {
- return;
- }
- current.cancel();
- }
- ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target);
- anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float value = (float) animation.getAnimatedValue();
- setDozeAlpha(inFront, value);
- }
- });
- anim.setInterpolator(interpolator);
- anim.setDuration(duration);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setCurrentAnimator(inFront, null);
- if (endRunnable != null) {
- endRunnable.run();
- }
- }
- });
- anim.start();
- setCurrentAnimator(inFront, anim);
- setCurrentTarget(inFront, target);
- }
-
- private float getCurrentTarget(boolean inFront) {
- return inFront ? mInFrontTarget : mBehindTarget;
- }
-
- private void setCurrentTarget(boolean inFront, float target) {
- if (inFront) {
- mInFrontTarget = target;
- } else {
- mBehindTarget = target;
- }
- }
-
- private Animator getCurrentAnimator(boolean inFront) {
- return inFront ? mInFrontAnimator : mBehindAnimator;
- }
-
- private void setCurrentAnimator(boolean inFront, Animator animator) {
- if (inFront) {
- mInFrontAnimator = animator;
- } else {
- mBehindAnimator = animator;
- }
- }
-
- private void setDozeAlpha(boolean inFront, float alpha) {
- if (mWakeAndUnlocking) {
- return;
- }
- if (inFront) {
- mScrimController.setDozeInFrontAlpha(alpha);
- } else {
- mScrimController.setDozeBehindAlpha(alpha);
- }
- }
-
- private float getDozeAlpha(boolean inFront) {
- return inFront
- ? mScrimController.getDozeInFrontAlpha()
- : mScrimController.getDozeBehindAlpha();
- }
-
- private void setDozeInFrontAlpha(float opacity) {
- setDozeInFrontAlphaDelayed(opacity, 0 /* delay */);
-
- }
-
- private void setDozeInFrontAlphaDelayed(float opacity, long delayMs) {
- if (mSetDozeInFrontAlphaDelayed != null) {
- mHandler.removeCallbacks(mSetDozeInFrontAlphaDelayed);
- mSetDozeInFrontAlphaDelayed = null;
- }
- if (delayMs <= 0) {
- mScrimController.setDozeInFrontAlpha(opacity);
- } else {
- mHandler.postDelayed(mSetDozeInFrontAlphaDelayed = () -> {
- setDozeInFrontAlpha(opacity);
- }, delayMs);
- }
- }
-
- private final Runnable mPulseIn = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
- + DozeLog.pulseReasonToString(mPulseReason));
- if (!mDozing) return;
- DozeLog.tracePulseStart(mPulseReason);
-
- // Signal that the pulse is ready to turn the screen on and draw.
- pulseStarted();
- }
- };
-
- private final Runnable mPulseInFinished = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
- if (!mDozing) return;
- mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
- mHandler.postDelayed(mPulseOutExtended,
- mDozeParameters.getPulseVisibleDurationExtended());
- mFullyPulsing = true;
- }
- };
-
private final Runnable mPulseOutExtended = new Runnable() {
@Override
public void run() {
@@ -354,38 +174,13 @@ public class DozeScrimController {
mHandler.removeCallbacks(mPulseOutExtended);
if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
if (!mDozing) return;
- startScrimAnimation(true /* inFront */, 1,
- mDozeParameters.getPulseOutDuration(),
- Interpolators.ALPHA_IN, mPulseOutFinishing);
- }
- };
-
- private final Runnable mPulseOutFinishing = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse out finished");
- DozeLog.tracePulseFinish();
- if (mDozeParameters.getAlwaysOn() && mDozing) {
- // Setting power states can block rendering. For AOD, delay finishing the pulse and
- // setting the power state until the fully black scrim had time to hit the
- // framebuffer.
- mHandler.postDelayed(mPulseOutFinished, 30);
- } else {
- mPulseOutFinished.run();
- }
- }
- };
-
- private final Runnable mPulseOutFinished = new Runnable() {
- @Override
- public void run() {
- // Signal that the pulse is all finished so we can turn the screen off now.
- DozeScrimController.this.pulseFinished();
- if (mDozeParameters.getAlwaysOn()) {
- // Setting power states can happen after we push out the frame. Make sure we
- // stay fully opaque until the power state request reaches the lower levels.
- setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100);
- }
+ mScrimController.transitionTo(ScrimState.AOD,
+ new ScrimController.Callback() {
+ @Override
+ public void onDisplayBlanked() {
+ pulseFinished();
+ }
+ });
}
};
-}
+} \ No newline at end of file
diff --git a/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 91369dbd..80d4061b 100644
--- a/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -181,9 +181,9 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback {
}
private boolean pulsingOrAod() {
- boolean pulsing = mDozeScrimController.isPulsing();
- boolean dozingWithScreenOn = mStatusBar.isDozing() && !mStatusBar.isScreenFullyOff();
- return pulsing || dozingWithScreenOn;
+ final ScrimState scrimState = mScrimController.getState();
+ return scrimState == ScrimState.AOD
+ || scrimState == ScrimState.PULSING;
}
@Override
@@ -246,15 +246,12 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback {
true /* allowEnterAnimation */);
} else if (mMode == MODE_WAKE_AND_UNLOCK){
Trace.beginSection("MODE_WAKE_AND_UNLOCK");
- mDozeScrimController.abortDoze();
} else {
Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
mUpdateMonitor.awakenFromDream();
}
mStatusBarWindowManager.setStatusBarFocusable(false);
mKeyguardViewMediator.onWakeAndUnlocking();
- mScrimController.setWakeAndUnlocking();
- mDozeScrimController.setWakeAndUnlocking();
if (mStatusBar.getNavigationBarView() != null) {
mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
}
@@ -269,6 +266,7 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback {
}
private void showBouncer() {
+ mScrimController.transitionTo(ScrimState.BOUNCER);
mStatusBarKeyguardViewManager.animateCollapsePanels(
FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
mPendingShowBouncer = false;
diff --git a/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index a6691b16..da809c12 100644
--- a/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -333,7 +333,7 @@ public class KeyguardStatusBarView extends RelativeLayout
return false;
}
- public void onOverlayChanged() {
+ public void onThemeChanged() {
@ColorInt int textColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
@ColorInt int iconColor = Utils.getDefaultColor(mContext, Color.luminance(textColor) < 0.5 ?
R.color.dark_mode_icon_color_single_tone :
diff --git a/com/android/systemui/statusbar/phone/LockIcon.java b/com/android/systemui/statusbar/phone/LockIcon.java
index 5c9446ce..34486dbc 100644
--- a/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/com/android/systemui/statusbar/phone/LockIcon.java
@@ -250,7 +250,7 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange
}
break;
case STATE_FACE_UNLOCK:
- iconRes = com.android.internal.R.drawable.ic_account_circle;
+ iconRes = R.drawable.ic_account_circle;
break;
case STATE_FINGERPRINT:
// If screen is off and device asleep, use the draw on animation so the first frame
diff --git a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index c9500363..b81a3b04 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -80,7 +80,8 @@ public final class NavigationBarTransitions extends BarTransitions {
@Override
protected boolean isLightsOut(int mode) {
- return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible);
+ return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible
+ && mode != MODE_WARNING);
}
public LightBarTransitionsController getLightTransitionsController() {
@@ -108,7 +109,9 @@ public final class NavigationBarTransitions extends BarTransitions {
// ok, everyone, stop it right there
navButtons.animate().cancel();
- final float navButtonsAlpha = lightsOut ? 0.6f : 1f;
+ // Bump percentage by 10% if dark.
+ float darkBump = mLightTransitionsController.getCurrentDarkIntensity() / 10;
+ final float navButtonsAlpha = lightsOut ? 0.6f + darkBump : 1f;
if (!animate) {
navButtons.setAlpha(navButtonsAlpha);
@@ -130,6 +133,9 @@ public final class NavigationBarTransitions extends BarTransitions {
for (int i = buttonDispatchers.size() - 1; i >= 0; i--) {
buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity);
}
+ if (mAutoDim) {
+ applyLightsOut(false, true);
+ }
}
private final View.OnTouchListener mLightsOutListener = new View.OnTouchListener() {
diff --git a/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 0f246c62..836efffb 100644
--- a/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -209,7 +209,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
}
}
- if (mDark && child instanceof StatusBarIconView) {
+ if (child instanceof StatusBarIconView) {
((StatusBarIconView) child).setDark(mDark, false, 0);
}
}
diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 86a8f411..17e35999 100644
--- a/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -239,6 +239,7 @@ public class NotificationPanelView extends PanelView implements
private ValueAnimator mDarkAnimator;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private boolean mUserSetupComplete;
+ private int mQsNotificationTopPadding;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -307,6 +308,8 @@ public class NotificationPanelView extends PanelView implements
R.dimen.max_notification_fadeout_height);
mIndicationBottomPadding = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_bottom_padding);
+ mQsNotificationTopPadding = getResources().getDimensionPixelSize(
+ R.dimen.qs_notification_keyguard_padding);
}
public void updateResources() {
@@ -330,7 +333,7 @@ public class NotificationPanelView extends PanelView implements
}
}
- public void onOverlayChanged() {
+ public void onThemeChanged() {
// Re-inflate the status view group.
int index = indexOfChild(mKeyguardStatusView);
removeView(mKeyguardStatusView);
@@ -818,7 +821,7 @@ public class NotificationPanelView extends PanelView implements
private float getQsExpansionFraction() {
return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
- / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+ / (mQsMaxExpansionHeight - mQsMinExpansionHeight));
}
@Override
@@ -1361,7 +1364,7 @@ public class NotificationPanelView extends PanelView implements
// take the maximum and linearly interpolate with the panel expansion for a nice motion.
int maxNotifications = mClockPositionResult.stackScrollerPadding
- mClockPositionResult.stackScrollerPaddingAdjustment;
- int maxQs = getTempQsMaxExpansion();
+ int maxQs = mQsMaxExpansionHeight + mQsNotificationTopPadding;
int max = mStatusBarState == StatusBarState.KEYGUARD
? Math.max(maxNotifications, maxQs)
: maxQs;
@@ -1375,7 +1378,7 @@ public class NotificationPanelView extends PanelView implements
// from a scrolled quick settings.
return interpolate(getQsExpansionFraction(),
mNotificationStackScroller.getIntrinsicPadding(),
- mQsMaxExpansionHeight);
+ mQsMaxExpansionHeight + mQsNotificationTopPadding);
} else {
return mQsExpansionHeight;
}
@@ -1544,7 +1547,7 @@ public class NotificationPanelView extends PanelView implements
/ (panelHeightQsExpanded - panelHeightQsCollapsed);
}
setQsExpansion(mQsMinExpansionHeight
- + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+ + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight));
}
updateExpandedHeight(expandedHeight);
updateHeader();
@@ -1566,14 +1569,6 @@ public class NotificationPanelView extends PanelView implements
}
}
- /**
- * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
- * collapsing QS / the panel when QS was scrolled
- */
- private int getTempQsMaxExpansion() {
- return mQsMaxExpansionHeight;
- }
-
private int calculatePanelHeightShade() {
int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
@@ -1596,6 +1591,10 @@ public class NotificationPanelView extends PanelView implements
}
int maxQsHeight = mQsMaxExpansionHeight;
+ if (mKeyguardShowing) {
+ maxQsHeight += mQsNotificationTopPadding;
+ }
+
// If an animation is changing the size of the QS panel, take the animated value.
if (mQsSizeChangeAnimator != null) {
maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
diff --git a/com/android/systemui/statusbar/phone/ScrimController.java b/com/android/systemui/statusbar/phone/ScrimController.java
index 702afa3a..3a367763 100644
--- a/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/com/android/systemui/statusbar/phone/ScrimController.java
@@ -25,7 +25,9 @@ import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
import android.os.Trace;
+import android.util.Log;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -34,12 +36,14 @@ import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener;
import com.android.internal.graphics.ColorUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -47,7 +51,10 @@ import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.stack.ViewState;
+import com.android.systemui.util.wakelock.DelayedWakeLock;
+import com.android.systemui.util.wakelock.WakeLock;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -56,33 +63,54 @@ import java.util.function.Consumer;
* security method gets shown).
*/
public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
- OnHeadsUpChangedListener, OnColorsChangedListener {
+ OnHeadsUpChangedListener, OnColorsChangedListener, Dumpable {
+
+ private static final String TAG = "ScrimController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
public static final long ANIMATION_DURATION = 220;
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
= new PathInterpolator(0f, 0, 0.7f, 1f);
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
= new PathInterpolator(0.3f, 0f, 0.8f, 1f);
- // Default alpha value for most scrims, if unsure use this constant
+ /**
+ * Default alpha value for most scrims.
+ */
public static final float GRADIENT_SCRIM_ALPHA = 0.45f;
- // A scrim varies its opacity based on a busyness factor, for example
- // how many notifications are currently visible.
+ /**
+ * A scrim varies its opacity based on a busyness factor, for example
+ * how many notifications are currently visible.
+ */
public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.70f;
+ /**
+ * The most common scrim, the one under the keyguard.
+ */
protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA;
+ /**
+ * We fade out the bottom scrim when the bouncer is visible.
+ */
protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
- private static final float SCRIM_IN_FRONT_ALPHA = GRADIENT_SCRIM_ALPHA_BUSY;
- private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
- private static final int TAG_KEY_ANIM = R.id.scrim;
+ /**
+ * Opacity of the scrim behind the bouncer (the one doing actual background protection.)
+ */
+ protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
+
+ static final int TAG_KEY_ANIM = R.id.scrim;
+ static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking;
private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
private static final float NOT_INITIALIZED = -1;
- private final LightBarController mLightBarController;
+ private ScrimState mState = ScrimState.UNINITIALIZED;
+ private final Context mContext;
protected final ScrimView mScrimBehind;
protected final ScrimView mScrimInFront;
- private final UnlockMethodCache mUnlockMethodCache;
private final View mHeadsUpScrim;
+ private final LightBarController mLightBarController;
+ private final UnlockMethodCache mUnlockMethodCache;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final DozeParameters mDozeParameters;
private final SysuiColorExtractor mColorExtractor;
private GradientColors mLockColors;
@@ -94,61 +122,53 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
- protected boolean mKeyguardShowing;
private float mFraction;
private boolean mDarkenWhileDragging;
- protected boolean mBouncerShowing;
- protected boolean mBouncerIsKeyguard = false;
- private boolean mWakeAndUnlocking;
protected boolean mAnimateChange;
private boolean mUpdatePending;
private boolean mTracking;
private boolean mAnimateKeyguardFadingOut;
- protected long mDurationOverride = -1;
+ protected long mAnimationDuration = -1;
private long mAnimationDelay;
private Runnable mOnAnimationFinished;
private boolean mDeferFinishedListener;
private final Interpolator mInterpolator = new DecelerateInterpolator();
- private boolean mDozing;
- private float mDozeInFrontAlpha;
- private float mDozeBehindAlpha;
private float mCurrentInFrontAlpha = NOT_INITIALIZED;
private float mCurrentBehindAlpha = NOT_INITIALIZED;
- private float mCurrentHeadsUpAlpha = NOT_INITIALIZED;
+ private int mCurrentInFrontTint;
+ private int mCurrentBehindTint;
private int mPinnedHeadsUpCount;
private float mTopHeadsUpDragAmount;
private View mDraggedHeadsUpView;
- private boolean mForceHideScrims;
- private boolean mSkipFirstFrame;
- private boolean mDontAnimateBouncerChanges;
private boolean mKeyguardFadingOutInProgress;
- private boolean mAnimatingDozeUnlock;
private ValueAnimator mKeyguardFadeoutAnimation;
- /** Wake up from AOD transition is starting; need fully opaque front scrim */
- private boolean mWakingUpFromAodStarting;
- /** Wake up from AOD transition is in progress; need black tint */
- private boolean mWakingUpFromAodInProgress;
- /** Wake up from AOD transition is animating; need to reset when animation finishes */
- private boolean mWakingUpFromAodAnimationRunning;
- private boolean mScrimsVisble;
+ private boolean mScrimsVisible;
private final Consumer<Boolean> mScrimVisibleListener;
+ private boolean mBlankScreen;
+ private boolean mScreenBlankingCallbackCalled;
+ private Callback mCallback;
+
+ private final WakeLock mWakeLock;
+ private boolean mWakeLockHeld;
public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
- ScrimView scrimInFront, View headsUpScrim,
- Consumer<Boolean> scrimVisibleListener) {
+ ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener,
+ DozeParameters dozeParameters) {
mScrimBehind = scrimBehind;
mScrimInFront = scrimInFront;
mHeadsUpScrim = headsUpScrim;
mScrimVisibleListener = scrimVisibleListener;
- final Context context = scrimBehind.getContext();
- mUnlockMethodCache = UnlockMethodCache.getInstance(context);
- mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+ mContext = scrimBehind.getContext();
+ mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
mLightBarController = lightBarController;
- mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
+ mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
+ mWakeLock = createWakeLock();
// Scrim alpha is initially set to the value on the resource but might be changed
// to make sure that text on top of it is legible.
mScrimBehindAlpha = mScrimBehindAlphaResValue;
+ mDozeParameters = dozeParameters;
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mColorExtractor.addOnColorsChangedListener(this);
@@ -158,159 +178,155 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
mNeedsDrawableColorUpdate = true;
+ final ScrimState[] states = ScrimState.values();
+ for (int i = 0; i < states.length; i++) {
+ states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+ states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
+ }
+ mState = ScrimState.UNINITIALIZED;
+
updateHeadsUpScrim(false);
updateScrims();
}
- public void setKeyguardShowing(boolean showing) {
- mKeyguardShowing = showing;
-
- // Showing/hiding the keyguard means that scrim colors have to be switched
- mNeedsDrawableColorUpdate = true;
- scheduleUpdate();
+ public void transitionTo(ScrimState state) {
+ transitionTo(state, null);
}
- protected void setScrimBehindValues(float scrimBehindAlphaKeyguard,
- float scrimBehindAlphaUnlocking) {
- mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
- mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking;
- scheduleUpdate();
- }
-
- public void onTrackingStarted() {
- mTracking = true;
- mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
- }
-
- public void onExpandingFinished() {
- mTracking = false;
- }
-
- public void setPanelExpansion(float fraction) {
- if (mFraction != fraction) {
- mFraction = fraction;
- scheduleUpdate();
- if (mPinnedHeadsUpCount != 0) {
- updateHeadsUpScrim(false);
- }
- if (mKeyguardFadeoutAnimation != null && mTracking) {
- mKeyguardFadeoutAnimation.cancel();
- }
+ public void transitionTo(ScrimState state, Callback callback) {
+ if (state == mState) {
+ return;
+ } else if (DEBUG) {
+ Log.d(TAG, "State changed to: " + state);
}
- }
-
- public void setBouncerShowing(boolean showing) {
- mBouncerShowing = showing;
- mAnimateChange = !mTracking && !mDontAnimateBouncerChanges && !mKeyguardFadingOutInProgress;
- scheduleUpdate();
- }
- /** Prepares the wakeUpFromAod animation (while turning on screen); Forces black scrims. */
- public void prepareWakeUpFromAod() {
- if (mWakingUpFromAodInProgress) {
- return;
+ if (state == ScrimState.UNINITIALIZED) {
+ throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
}
- mWakingUpFromAodInProgress = true;
- mWakingUpFromAodStarting = true;
- mAnimateChange = false;
- scheduleUpdate();
- onPreDraw();
- }
- /** Starts the wakeUpFromAod animation (once screen is on); animate to transparent scrims. */
- public void wakeUpFromAod() {
- if (mWakeAndUnlocking || mAnimateKeyguardFadingOut) {
- // Wake and unlocking has a separate transition that must not be interfered with.
- mWakingUpFromAodStarting = false;
- mWakingUpFromAodInProgress = false;
- return;
+ if (mCallback != null) {
+ mCallback.onCancelled();
}
- if (mWakingUpFromAodStarting) {
- mWakingUpFromAodInProgress = true;
- mWakingUpFromAodStarting = false;
- mAnimateChange = true;
- scheduleUpdate();
+ mCallback = callback;
+
+ state.prepare(mState);
+ mScreenBlankingCallbackCalled = false;
+ mAnimationDelay = 0;
+ mBlankScreen = state.getBlanksScreen();
+ mAnimateChange = state.getAnimateChange();
+ mAnimationDuration = state.getAnimationDuration();
+ mCurrentInFrontTint = state.getFrontTint();
+ mCurrentBehindTint = state.getBehindTint();
+ mCurrentInFrontAlpha = state.getFrontAlpha();
+ mCurrentBehindAlpha = state.getBehindAlpha();
+
+ // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
+ // to do the same when you're just showing the brightness mirror.
+ mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
+
+ if (mKeyguardFadeoutAnimation != null) {
+ mKeyguardFadeoutAnimation.cancel();
}
- }
- public void setWakeAndUnlocking() {
- mWakeAndUnlocking = true;
- mAnimatingDozeUnlock = true;
- mWakingUpFromAodStarting = false;
- mWakingUpFromAodInProgress = false;
- scheduleUpdate();
- }
+ mState = state;
- public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished,
- boolean skipFirstFrame) {
- mWakeAndUnlocking = false;
- mAnimateKeyguardFadingOut = true;
- mDurationOverride = duration;
- mAnimationDelay = delay;
- mAnimateChange = true;
- mSkipFirstFrame = skipFirstFrame;
- mOnAnimationFinished = onAnimationFinished;
+ // Do not let the device sleep until we're done with all animations
+ if (!mWakeLockHeld) {
+ if (mWakeLock != null) {
+ mWakeLockHeld = true;
+ mWakeLock.acquire();
+ } else {
+ Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+ }
+ }
if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
scheduleUpdate();
-
- // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
- // the changes we just scheduled.
- onPreDraw();
} else {
-
// In case the user isn't unlocked, make sure to delay a bit because the system is hosed
- // with too many things in this case, in order to not skip the initial frames.
+ // with too many things at this case, in order to not skip the initial frames.
mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
+ mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
}
}
- public void abortKeyguardFadingOut() {
- if (mAnimateKeyguardFadingOut) {
- endAnimateKeyguardFadingOut(true /* force */);
- }
+ public ScrimState getState() {
+ return mState;
}
- public void animateKeyguardUnoccluding(long duration) {
- mAnimateChange = false;
- setScrimBehindAlpha(0f);
- mAnimateChange = true;
+ protected void setScrimBehindValues(float scrimBehindAlphaKeyguard,
+ float scrimBehindAlphaUnlocking) {
+ mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
+ mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking;
+ ScrimState[] states = ScrimState.values();
+ for (int i = 0; i < states.length; i++) {
+ states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
+ }
scheduleUpdate();
- mDurationOverride = duration;
}
- public void animateGoingToFullShade(long delay, long duration) {
- mDurationOverride = duration;
- mAnimationDelay = delay;
- mAnimateChange = true;
- scheduleUpdate();
+ public void onTrackingStarted() {
+ mTracking = true;
+ mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
}
- public void setDozing(boolean dozing) {
- if (mDozing != dozing) {
- mDozing = dozing;
- scheduleUpdate();
- }
+ public void onExpandingFinished() {
+ mTracking = false;
}
- public void setDozeInFrontAlpha(float alpha) {
- mDozeInFrontAlpha = alpha;
- updateScrimColor(mScrimInFront);
- }
+ /**
+ * Current state of the shade expansion when pulling it from the top.
+ * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
+ *
+ * The expansion fraction is tied to the scrim opacity.
+ *
+ * @param fraction From 0 to 1 where 0 means collapse and 1 expanded.
+ */
+ public void setPanelExpansion(float fraction) {
+ if (mFraction != fraction) {
+ mFraction = fraction;
- public void setDozeBehindAlpha(float alpha) {
- mDozeBehindAlpha = alpha;
- updateScrimColor(mScrimBehind);
- }
+ if (mState == ScrimState.UNLOCKED) {
+ // Darken scrim as you pull down the shade when unlocked
+ float behindFraction = getInterpolatedFraction();
+ behindFraction = (float) Math.pow(behindFraction, 0.8f);
+ mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard;
+ mCurrentInFrontAlpha = 0;
+ } else if (mState == ScrimState.KEYGUARD) {
+ if (mUpdatePending) {
+ return;
+ }
- public float getDozeBehindAlpha() {
- return mDozeBehindAlpha;
- }
+ // Either darken of make the scrim transparent when you
+ // pull down the shade
+ float interpolatedFract = getInterpolatedFraction();
+ if (mDarkenWhileDragging) {
+ mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking,
+ mScrimBehindAlphaKeyguard, interpolatedFract);
+ mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED;
+ } else {
+ mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard,
+ interpolatedFract);
+ mCurrentInFrontAlpha = 0;
+ }
+ } else {
+ Log.w(TAG, "Invalid state, cannot set panel expansion when: " + mState);
+ return;
+ }
- public float getDozeInFrontAlpha() {
- return mDozeInFrontAlpha;
+ if (mPinnedHeadsUpCount != 0) {
+ updateHeadsUpScrim(false);
+ }
+
+ updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha);
+ updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha);
+ }
}
+ /**
+ * Keyguard and shade scrim opacity varies according to how many notifications are visible.
+ * @param notificationCount Number of visible notifications.
+ */
public void setNotificationCount(int notificationCount) {
final float maxNotificationDensity = 3;
float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f);
@@ -319,15 +335,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
notificationDensity);
if (mScrimBehindAlphaKeyguard != newAlpha) {
mScrimBehindAlphaKeyguard = newAlpha;
- mAnimateChange = true;
- scheduleUpdate();
- }
- }
- private float getScrimInFrontAlpha() {
- return mKeyguardUpdateMonitor.needsSlowUnlockTransition()
- ? SCRIM_IN_FRONT_ALPHA_LOCKED
- : SCRIM_IN_FRONT_ALPHA;
+ if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
+ scheduleUpdate();
+ }
+ }
}
/**
@@ -352,7 +364,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
if (mNeedsDrawableColorUpdate) {
mNeedsDrawableColorUpdate = false;
final GradientColors currentScrimColors;
- if (mKeyguardShowing) {
+ if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
// Always animate color changes if we're seeing the keyguard
mScrimInFront.setColors(mLockColors, true /* animated */);
mScrimBehind.setColors(mLockColors, true /* animated */);
@@ -375,77 +387,31 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
mLightBarController.setScrimColor(mScrimInFront.getColors());
}
- if (mAnimateKeyguardFadingOut || mForceHideScrims) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(0f);
- } else if (mWakeAndUnlocking) {
- // During wake and unlock, we first hide everything behind a black scrim, which then
- // gets faded out from animateKeyguardFadingOut. This must never be animated.
- mAnimateChange = false;
- if (mDozing) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(1f);
- } else {
- setScrimInFrontAlpha(1f);
- setScrimBehindAlpha(0f);
- }
- } else if (!mKeyguardShowing && !mBouncerShowing && !mWakingUpFromAodStarting) {
- updateScrimNormal();
- setScrimInFrontAlpha(0);
- } else {
- updateScrimKeyguard();
- }
- mAnimateChange = false;
+ setScrimInFrontAlpha(mCurrentInFrontAlpha);
+ setScrimBehindAlpha(mCurrentBehindAlpha);
+
dispatchScrimsVisible();
}
private void dispatchScrimsVisible() {
boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0;
- if (mScrimsVisble != scrimsVisible) {
- mScrimsVisble = scrimsVisible;
+ if (mScrimsVisible != scrimsVisible) {
+ mScrimsVisible = scrimsVisible;
mScrimVisibleListener.accept(scrimsVisible);
}
}
- private void updateScrimKeyguard() {
- if (mTracking && mDarkenWhileDragging) {
- float behindFraction = Math.max(0, Math.min(mFraction, 1));
- float fraction = 1 - behindFraction;
- fraction = (float) Math.pow(fraction, 0.8f);
- behindFraction = (float) Math.pow(behindFraction, 0.8f);
- setScrimInFrontAlpha(fraction * getScrimInFrontAlpha());
- setScrimBehindAlpha(behindFraction * mScrimBehindAlphaKeyguard);
- } else if (mBouncerShowing && !mBouncerIsKeyguard) {
- setScrimInFrontAlpha(getScrimInFrontAlpha());
- updateScrimNormal();
- } else if (mBouncerShowing) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(mScrimBehindAlpha);
- } else {
- float fraction = Math.max(0, Math.min(mFraction, 1));
- if (mWakingUpFromAodStarting) {
- setScrimInFrontAlpha(1f);
- } else {
- setScrimInFrontAlpha(0f);
- }
- setScrimBehindAlpha(fraction
- * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
- + mScrimBehindAlphaUnlocking);
- }
- }
-
- private void updateScrimNormal() {
+ private float getInterpolatedFraction() {
float frac = mFraction;
// let's start this 20% of the way down the screen
frac = frac * 1.2f - 0.2f;
if (frac <= 0) {
- setScrimBehindAlpha(0);
+ return 0;
} else {
// woo, special effects
- final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
- setScrimBehindAlpha(k * mScrimBehindAlpha);
+ return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
}
}
@@ -455,102 +421,76 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
private void setScrimInFrontAlpha(float alpha) {
setScrimAlpha(mScrimInFront, alpha);
- if (alpha == 0f) {
- mScrimInFront.setClickable(false);
- } else {
- // Eat touch events (unless dozing).
- mScrimInFront.setClickable(!mDozing);
- }
}
private void setScrimAlpha(View scrim, float alpha) {
- updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
- }
-
- protected float getDozeAlpha(View scrim) {
- return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
- }
-
- protected float getCurrentScrimAlpha(View scrim) {
- return scrim == mScrimBehind ? mCurrentBehindAlpha
- : scrim == mScrimInFront ? mCurrentInFrontAlpha
- : mCurrentHeadsUpAlpha;
- }
-
- private void setCurrentScrimAlpha(View scrim, float alpha) {
- if (scrim == mScrimBehind) {
- mCurrentBehindAlpha = alpha;
- mLightBarController.setScrimAlpha(mCurrentBehindAlpha);
- } else if (scrim == mScrimInFront) {
- mCurrentInFrontAlpha = alpha;
+ if (alpha == 0f) {
+ scrim.setClickable(false);
} else {
- alpha = Math.max(0.0f, Math.min(1.0f, alpha));
- mCurrentHeadsUpAlpha = alpha;
+ // Eat touch events (unless dozing).
+ scrim.setClickable(!(mState == ScrimState.AOD));
}
+ updateScrim(mAnimateChange, scrim, alpha);
}
- private void updateScrimColor(View scrim) {
- float alpha1 = getCurrentScrimAlpha(scrim);
+ private void updateScrimColor(View scrim, float alpha, int tint) {
+ alpha = Math.max(0, Math.min(1.0f, alpha));
if (scrim instanceof ScrimView) {
ScrimView scrimView = (ScrimView) scrim;
- float dozeAlpha = getDozeAlpha(scrim);
- float alpha = 1 - (1 - alpha1) * (1 - dozeAlpha);
- alpha = Math.max(0, Math.min(1.0f, alpha));
- scrimView.setViewAlpha(alpha);
Trace.traceCounter(Trace.TRACE_TAG_APP,
scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
(int) (alpha * 255));
- int dozeTint = Color.TRANSPARENT;
-
- boolean dozing = mAnimatingDozeUnlock || mDozing;
- boolean frontScrimDozing = mWakingUpFromAodInProgress;
- if (dozing || frontScrimDozing && scrim == mScrimInFront) {
- dozeTint = Color.BLACK;
- }
Trace.traceCounter(Trace.TRACE_TAG_APP,
scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
- dozeTint == Color.BLACK ? 1 : 0);
+ Color.alpha(tint));
- scrimView.setTint(dozeTint);
+ scrimView.setTint(tint);
+ scrimView.setViewAlpha(alpha);
} else {
- scrim.setAlpha(alpha1);
+ scrim.setAlpha(alpha);
}
dispatchScrimsVisible();
}
- private void startScrimAnimation(final View scrim, float target) {
- float current = getCurrentScrimAlpha(scrim);
- ValueAnimator anim = ValueAnimator.ofFloat(current, target);
+ private int getCurrentScrimTint(View scrim) {
+ return scrim == mScrimInFront ? mCurrentInFrontTint : mCurrentBehindTint;
+ }
+
+ private void startScrimAnimation(final View scrim, float current, float target) {
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
+ Color.TRANSPARENT;
anim.addUpdateListener(animation -> {
- float alpha = (float) animation.getAnimatedValue();
- setCurrentScrimAlpha(scrim, alpha);
- updateScrimColor(scrim);
+ final float animAmount = (float) animation.getAnimatedValue();
+ final int finalScrimTint = scrim == mScrimInFront ?
+ mCurrentInFrontTint : mCurrentBehindTint;
+ float alpha = MathUtils.lerp(current, target, animAmount);
+ int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
+ updateScrimColor(scrim, alpha, tint);
dispatchScrimsVisible();
});
anim.setInterpolator(getInterpolator());
anim.setStartDelay(mAnimationDelay);
- anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
+ anim.setDuration(mAnimationDuration);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- if (!mDeferFinishedListener && mOnAnimationFinished != null) {
- mOnAnimationFinished.run();
- mOnAnimationFinished = null;
- }
if (mKeyguardFadingOutInProgress) {
mKeyguardFadeoutAnimation = null;
mKeyguardFadingOutInProgress = false;
- mAnimatingDozeUnlock = false;
- }
- if (mWakingUpFromAodAnimationRunning && !mDeferFinishedListener) {
- mWakingUpFromAodAnimationRunning = false;
- mWakingUpFromAodInProgress = false;
}
+ onFinished();
+
scrim.setTag(TAG_KEY_ANIM, null);
scrim.setTag(TAG_KEY_ANIM_TARGET, null);
dispatchScrimsVisible();
+
+ if (!mDeferFinishedListener && mOnAnimationFinished != null) {
+ mOnAnimationFinished.run();
+ mOnAnimationFinished = null;
+ }
}
});
anim.start();
@@ -558,12 +498,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
mKeyguardFadingOutInProgress = true;
mKeyguardFadeoutAnimation = anim;
}
- if (mWakingUpFromAodInProgress) {
- mWakingUpFromAodAnimationRunning = true;
- }
- if (mSkipFirstFrame) {
- anim.setCurrentPlayTime(16);
- }
scrim.setTag(TAG_KEY_ANIM, anim);
scrim.setTag(TAG_KEY_ANIM_TARGET, target);
}
@@ -582,19 +516,33 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
public boolean onPreDraw() {
mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
mUpdatePending = false;
- if (mDontAnimateBouncerChanges) {
- mDontAnimateBouncerChanges = false;
+ if (mCallback != null) {
+ mCallback.onStart();
}
updateScrims();
- mDurationOverride = -1;
- mAnimationDelay = 0;
- mSkipFirstFrame = false;
// Make sure that we always call the listener even if we didn't start an animation.
endAnimateKeyguardFadingOut(false /* force */);
return true;
}
+ private void onFinished() {
+ if (mWakeLockHeld) {
+ mWakeLock.release();
+ mWakeLockHeld = false;
+ }
+ if (mCallback != null) {
+ mCallback.onFinished();
+ mCallback = null;
+ }
+ // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
+ // At the end of the animation we need to remove the tint.
+ if (mState == ScrimState.UNLOCKED) {
+ mCurrentInFrontTint = Color.TRANSPARENT;
+ mCurrentBehindTint = Color.TRANSPARENT;
+ }
+ }
+
private void endAnimateKeyguardFadingOut(boolean force) {
mAnimateKeyguardFadingOut = false;
if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) {
@@ -603,8 +551,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
mOnAnimationFinished = null;
}
mKeyguardFadingOutInProgress = false;
- if (!mWakeAndUnlocking || force)
- mAnimatingDozeUnlock = false;
}
}
@@ -641,16 +587,19 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
}
private void updateHeadsUpScrim(boolean animate) {
- updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha);
+ updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha());
}
- private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
- if (mKeyguardFadingOutInProgress && mKeyguardFadeoutAnimation.getCurrentPlayTime() != 0) {
- return;
- }
+ @VisibleForTesting
+ void setOnAnimationFinished(Runnable onAnimationFinished) {
+ mOnAnimationFinished = onAnimationFinished;
+ }
+
+ private void updateScrim(boolean animate, View scrim, float alpha) {
+ final float currentAlpha = scrim instanceof ScrimView ? ((ScrimView) scrim).getViewAlpha()
+ : scrim.getAlpha();
- ValueAnimator previousAnimator = ViewState.getChildTag(scrim,
- TAG_KEY_ANIM);
+ ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
float animEndValue = -1;
if (previousAnimator != null) {
if (animate || alpha == currentAlpha) {
@@ -664,9 +613,37 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA);
}
}
- if (alpha != currentAlpha && alpha != animEndValue) {
+
+ final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null;
+ if (mBlankScreen || blankingInProgress) {
+ if (!blankingInProgress) {
+ blankDisplay();
+ }
+ return;
+ } else if (!mScreenBlankingCallbackCalled) {
+ // Not blanking the screen. Letting the callback know that we're ready
+ // to replace what was on the screen before.
+ if (mCallback != null) {
+ mCallback.onDisplayBlanked();
+ mScreenBlankingCallbackCalled = true;
+ }
+ }
+
+ // TODO factor mLightBarController out of this class
+ if (scrim == mScrimBehind) {
+ mLightBarController.setScrimAlpha(alpha);
+ }
+
+ final ScrimView scrimView = scrim instanceof ScrimView ? (ScrimView) scrim : null;
+ final boolean wantsAlphaUpdate = alpha != currentAlpha && alpha != animEndValue;
+ final boolean wantsTintUpdate = scrimView != null
+ && scrimView.getTint() != getCurrentScrimTint(scrimView);
+
+ if (wantsAlphaUpdate || wantsTintUpdate) {
if (animate) {
- startScrimAnimation(scrim, alpha);
+ final float fromAlpha = scrimView == null ? scrim.getAlpha()
+ : scrimView.getViewAlpha();
+ startScrimAnimation(scrim, fromAlpha, alpha);
scrim.setTag(TAG_START_ALPHA, currentAlpha);
scrim.setTag(TAG_END_ALPHA, alpha);
} else {
@@ -685,13 +662,62 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
} else {
// update the alpha directly
- setCurrentScrimAlpha(scrim, alpha);
- updateScrimColor(scrim);
+ updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
+ onFinished();
+ }
+ }
+ } else {
+ onFinished();
+ }
+ }
+
+ private void blankDisplay() {
+ final float initialAlpha = mScrimInFront.getViewAlpha();
+ final int initialTint = mScrimInFront.getTint();
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ anim.addUpdateListener(animation -> {
+ final float amount = (float) animation.getAnimatedValue();
+ float animAlpha = MathUtils.lerp(initialAlpha, 1, amount);
+ int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount);
+ updateScrimColor(mScrimInFront, animAlpha, animTint);
+ dispatchScrimsVisible();
+ });
+ anim.setInterpolator(getInterpolator());
+ anim.setDuration(mDozeParameters.getPulseInDuration());
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCallback != null) {
+ mCallback.onDisplayBlanked();
+ mScreenBlankingCallbackCalled = true;
}
+ Runnable blankingCallback = () -> {
+ mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null);
+ mBlankScreen = false;
+ // Try again.
+ updateScrims();
+ };
+
+ // Setting power states can happen after we push out the frame. Make sure we
+ // stay fully opaque until the power state request reaches the lower levels.
+ getHandler().postDelayed(blankingCallback, 100);
+
}
+ });
+ anim.start();
+ mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim);
+
+ // Finish animation if we're already at its final state
+ if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) {
+ anim.end();
}
}
+ @VisibleForTesting
+ protected Handler getHandler() {
+ return Handler.getMain();
+ }
+
/**
* Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means
* the heads up is in its resting space and 1 means it's fully dragged out.
@@ -719,23 +745,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
return alpha * expandFactor;
}
- public void forceHideScrims(boolean hide, boolean animated) {
- mForceHideScrims = hide;
- mAnimateChange = animated;
- scheduleUpdate();
- }
-
- public void dontAnimateBouncerChangesUntilNextFrame() {
- mDontAnimateBouncerChanges = true;
- }
-
public void setExcludedBackgroundArea(Rect area) {
mScrimBehind.setExcludedArea(area);
}
public int getBackgroundColor() {
int color = mLockColors.getMainColor();
- return Color.argb((int) (mScrimBehind.getAlpha() * Color.alpha(color)),
+ return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)),
Color.red(color), Color.green(color), Color.blue(color));
}
@@ -764,27 +780,41 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
}
if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
- ColorExtractor.TYPE_DARK, mKeyguardShowing);
+ ColorExtractor.TYPE_DARK, mState != ScrimState.UNLOCKED);
mNeedsDrawableColorUpdate = true;
scheduleUpdate();
}
}
- public void dump(PrintWriter pw) {
- pw.println(" ScrimController:");
+ @VisibleForTesting
+ protected WakeLock createWakeLock() {
+ return new DelayedWakeLock(getHandler(),
+ WakeLock.createPartial(mContext, "Doze"));
+ }
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(" ScrimController:");
+ pw.print(" state:"); pw.println(mState);
pw.print(" frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
- pw.print(" dozeAlpha="); pw.print(mDozeInFrontAlpha);
pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
pw.print(" backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
- pw.print(" dozeAlpha="); pw.print(mDozeBehindAlpha);
pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
- pw.print(" mBouncerShowing="); pw.println(mBouncerShowing);
pw.print(" mTracking="); pw.println(mTracking);
- pw.print(" mForceHideScrims="); pw.println(mForceHideScrims);
+ }
+
+ public interface Callback {
+ default void onStart() {
+ }
+ default void onDisplayBlanked() {
+ }
+ default void onFinished() {
+ }
+ default void onCancelled() {
+ }
}
}
diff --git a/com/android/systemui/statusbar/phone/ScrimState.java b/com/android/systemui/statusbar/phone/ScrimState.java
new file mode 100644
index 00000000..0db98f37
--- /dev/null
+++ b/com/android/systemui/statusbar/phone/ScrimState.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.statusbar.phone;
+
+import android.graphics.Color;
+import android.os.Trace;
+
+import com.android.systemui.statusbar.ScrimView;
+
+/**
+ * Possible states of the ScrimController state machine.
+ */
+public enum ScrimState {
+
+ /**
+ * Initial state.
+ */
+ UNINITIALIZED,
+
+ /**
+ * On the lock screen.
+ */
+ KEYGUARD {
+
+ @Override
+ public void prepare(ScrimState previousState) {
+ // DisplayPowerManager will blank the screen, we'll just
+ // set our scrim to black in this frame to avoid flickering and
+ // fade it out afterwards.
+ mBlankScreen = previousState == ScrimState.AOD;
+ if (previousState == ScrimState.AOD) {
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ }
+ mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
+ mCurrentInFrontAlpha = 0;
+ }
+ },
+
+ /**
+ * Showing password challenge.
+ */
+ BOUNCER {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = ScrimController.SCRIM_BEHIND_ALPHA_UNLOCKING;
+ mCurrentInFrontAlpha = ScrimController.SCRIM_IN_FRONT_ALPHA_LOCKED;
+ }
+ },
+
+ /**
+ * Changing screen brightness from quick settings.
+ */
+ BRIGHTNESS_MIRROR {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 0;
+ mCurrentInFrontAlpha = 0;
+ }
+ },
+
+ /**
+ * Always on display or screen off.
+ */
+ AOD {
+ @Override
+ public void prepare(ScrimState previousState) {
+ if (previousState == ScrimState.PULSING) {
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ }
+ final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
+ mBlankScreen = previousState == ScrimState.PULSING;
+ mCurrentBehindAlpha = 1;
+ mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ // DisplayPowerManager will blank the screen for us, we just need
+ // to set our state.
+ mAnimateChange = false;
+ }
+ },
+
+ /**
+ * When phone wakes up because you received a notification.
+ */
+ PULSING {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 1;
+ mCurrentInFrontAlpha = 0;
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ mBlankScreen = true;
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ }
+ },
+
+ /**
+ * Unlocked on top of an app (launcher or any other activity.)
+ */
+ UNLOCKED {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 0;
+ mCurrentInFrontAlpha = 0;
+ mAnimationDuration = StatusBar.FADE_KEYGUARD_DURATION;
+
+ if (previousState == ScrimState.AOD) {
+ // Fade from black to transparent when coming directly from AOD
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1, Color.BLACK);
+ // Scrims should still be black at the end of the transition.
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ mBlankScreen = true;
+ } else {
+ // Scrims should still be black at the end of the transition.
+ mCurrentInFrontTint = Color.TRANSPARENT;
+ mCurrentBehindTint = Color.TRANSPARENT;
+ mBlankScreen = false;
+ }
+ }
+ };
+
+ boolean mBlankScreen = false;
+ long mAnimationDuration = ScrimController.ANIMATION_DURATION;
+ int mCurrentInFrontTint = Color.TRANSPARENT;
+ int mCurrentBehindTint = Color.TRANSPARENT;
+ boolean mAnimateChange = true;
+ float mCurrentInFrontAlpha;
+ float mCurrentBehindAlpha;
+ float mAodFrontScrimAlpha;
+ float mScrimBehindAlphaKeyguard;
+ ScrimView mScrimInFront;
+ ScrimView mScrimBehind;
+ DozeParameters mDozeParameters;
+
+ public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+ mScrimInFront = scrimInFront;
+ mScrimBehind = scrimBehind;
+ mDozeParameters = dozeParameters;
+ }
+
+ public void prepare(ScrimState previousState) {
+ }
+
+ public float getFrontAlpha() {
+ return mCurrentInFrontAlpha;
+ }
+
+ public float getBehindAlpha() {
+ return mCurrentBehindAlpha;
+ }
+
+ public int getFrontTint() {
+ return mCurrentInFrontTint;
+ }
+
+ public int getBehindTint() {
+ return mCurrentBehindTint;
+ }
+
+ public long getAnimationDuration() {
+ return mAnimationDuration;
+ }
+
+ public boolean getBlanksScreen() {
+ return mBlankScreen;
+ }
+
+ public void updateScrimColor(ScrimView scrim, float alpha, int tint) {
+ Trace.traceCounter(Trace.TRACE_TAG_APP,
+ scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+ (int) (alpha * 255));
+
+ Trace.traceCounter(Trace.TRACE_TAG_APP,
+ scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+ Color.alpha(tint));
+
+ scrim.setTint(tint);
+ scrim.setViewAlpha(alpha);
+ }
+
+ public boolean getAnimateChange() {
+ return mAnimateChange;
+ }
+
+ public void setAodFrontScrimAlpha(float aodFrontScrimAlpha) {
+ mAodFrontScrimAlpha = aodFrontScrimAlpha;
+ }
+
+ public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
+ mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
+ }
+} \ No newline at end of file
diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java
index 67756159..dc8100f5 100644
--- a/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/com/android/systemui/statusbar/phone/StatusBar.java
@@ -79,7 +79,6 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.media.AudioAttributes;
import android.media.MediaMetadata;
-import android.media.session.MediaSessionManager;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.AsyncTask;
@@ -239,6 +238,7 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout
.OnChildLocationsChangedListener;
import com.android.systemui.util.NotificationChannels;
import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.wakelock.WakeLock;
import com.android.systemui.volume.VolumeComponent;
import java.io.FileDescriptor;
@@ -387,6 +387,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private VolumeComponent mVolumeComponent;
private BrightnessMirrorController mBrightnessMirrorController;
+ private boolean mBrightnessMirrorVisible;
protected FingerprintUnlockController mFingerprintUnlockController;
private LightBarController mLightBarController;
protected LockscreenWallpaper mLockscreenWallpaper;
@@ -647,6 +648,31 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
+ // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
+ // this animation is tied to the scrim for historic reasons.
+ // TODO: notify when keyguard has faded away instead of the scrim.
+ private final ScrimController.Callback mUnlockScrimCallback = new ScrimController
+ .Callback() {
+ @Override
+ public void onFinished() {
+ notifyKeyguardState();
+ }
+
+ @Override
+ public void onCancelled() {
+ notifyKeyguardState();
+ }
+
+ private void notifyKeyguardState() {
+ if (mStatusBarKeyguardViewManager == null) {
+ Log.w(TAG, "Tried to notify keyguard visibility when "
+ + "mStatusBarKeyguardViewManager was null");
+ return;
+ }
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }
+ };
+
private NotificationMessagingUtil mMessagingUtil;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private UserSwitcherController mUserSwitcherController;
@@ -919,7 +945,14 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
mGutsManager = new NotificationGutsManager(this, mStackScroller,
- mCheckSaveListener, mContext);
+ mCheckSaveListener, mContext,
+ key -> {
+ try {
+ mBarService.onNotificationSettingsViewed(key);
+ } catch (RemoteException e) {
+ // if we're here we're dead
+ }
+ });
mNotificationPanel.setStatusBar(this);
mNotificationPanel.setGroupManager(mGroupManager);
mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -1039,7 +1072,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mStatusBarWindowManager != null) {
mStatusBarWindowManager.setScrimsVisible(scrimsVisible);
}
- });
+ }, new DozeParameters(mContext));
if (mScrimSrcModeEnabled) {
Runnable runnable = () -> {
boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
@@ -1075,7 +1108,10 @@ public class StatusBar extends SystemUI implements DemoMode,
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
- mScrimController);
+ (visible) -> {
+ mBrightnessMirrorVisible = visible;
+ updateScrimController();
+ });
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragment) {
@@ -1222,13 +1258,13 @@ public class StatusBar extends SystemUI implements DemoMode,
reevaluateStyles();
}
- private void reinflateViews() {
+ private void onThemeChanged() {
reevaluateStyles();
// Clock and bottom icons
- mNotificationPanel.onOverlayChanged();
+ mNotificationPanel.onThemeChanged();
// The status bar on the keyguard is a special layout.
- if (mKeyguardStatusBar != null) mKeyguardStatusBar.onOverlayChanged();
+ if (mKeyguardStatusBar != null) mKeyguardStatusBar.onThemeChanged();
// Recreate Indication controller because internal references changed
mKeyguardIndicationController =
SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
@@ -1239,11 +1275,8 @@ public class StatusBar extends SystemUI implements DemoMode,
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mKeyguardIndicationController.setVisible(mState == StatusBarState.KEYGUARD);
mKeyguardIndicationController.setDozing(mDozing);
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.onOverlayChanged();
- }
if (mStatusBarKeyguardViewManager != null) {
- mStatusBarKeyguardViewManager.onOverlayChanged();
+ mStatusBarKeyguardViewManager.onThemeChanged();
}
if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
@@ -1258,6 +1291,13 @@ public class StatusBar extends SystemUI implements DemoMode,
updateEmptyShadeView();
}
+ @Override
+ public void onOverlayChanged() {
+ if (mBrightnessMirrorController != null) {
+ mBrightnessMirrorController.onOverlayChanged();
+ }
+ }
+
private void updateNotificationsOnDensityOrFontScaleChanged() {
ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
for (int i = 0; i < activeNotifications.size(); i++) {
@@ -1447,8 +1487,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mDozeScrimController, keyguardViewMediator,
mScrimController, this, UnlockMethodCache.getInstance(mContext));
mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
- getBouncerContainer(), mScrimController,
- mFingerprintUnlockController);
+ getBouncerContainer(), mFingerprintUnlockController);
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -1470,6 +1509,11 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
}
+ try {
+ mBarService.onNotificationDirectReplied(entry.key);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ }
}
});
@@ -1785,9 +1829,14 @@ public class StatusBar extends SystemUI implements DemoMode,
final int id = n.getId();
final int userId = n.getUserId();
try {
- // TODO: record actual dismissal surface
+ int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+ if (isHeadsUp(n.getKey())) {
+ dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+ } else if (mStackScroller.hasPulsingNotifications()) {
+ dismissalSurface = NotificationStats.DISMISSAL_AOD;
+ }
mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(),
- NotificationStats.DISMISSAL_OTHER);
+ dismissalSurface);
if (FORCE_REMOTE_INPUT_HISTORY
&& mKeysKeptForRemoteInput.contains(n.getKey())) {
mKeysKeptForRemoteInput.remove(n.getKey());
@@ -2620,7 +2669,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public boolean isPulsing() {
- return mDozeScrimController.isPulsing();
+ return mDozeScrimController != null && mDozeScrimController.isPulsing();
}
@Override
@@ -3328,7 +3377,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
if (mScrimController != null) {
- mScrimController.dump(pw);
+ mScrimController.dump(fd, pw, args);
}
if (DUMPTRUCK) {
@@ -3393,7 +3442,19 @@ public class StatusBar extends SystemUI implements DemoMode,
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
- mRemoteInputController = new RemoteInputController(mHeadsUpManager);
+ mRemoteInputController = new RemoteInputController(new RemoteInputController.Delegate() {
+ public void setRemoteInputActive(NotificationData.Entry entry,
+ boolean remoteInputActive) {
+ mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+ }
+ public void lockScrollTo(NotificationData.Entry entry) {
+ mStackScroller.lockScrollTo(entry.row);
+ }
+ public void requestDisallowLongPressAndDismiss() {
+ mStackScroller.requestDisallowLongPress();
+ mStackScroller.requestDisallowDismiss();
+ }
+ });
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
@@ -4061,7 +4122,6 @@ public class StatusBar extends SystemUI implements DemoMode,
releaseGestureWakeLock();
runLaunchTransitionEndRunnable();
mLaunchTransitionFadingAway = false;
- mScrimController.forceHideScrims(false /* hide */, false /* animated */);
updateMediaMetaData(true /* metaDataChanged */, true);
}
@@ -4094,7 +4154,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (beforeFading != null) {
beforeFading.run();
}
- mScrimController.forceHideScrims(true /* hide */, false /* animated */);
+ updateScrimController();
updateMediaMetaData(false, true);
mNotificationPanel.setAlpha(1);
mStackScroller.setParentNotFullyVisible(true);
@@ -4125,6 +4185,13 @@ public class StatusBar extends SystemUI implements DemoMode,
.setStartDelay(0)
.setDuration(FADE_KEYGUARD_DURATION_PULSING)
.setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ hideKeyguard();
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }
+ })
.start();
}
@@ -4132,7 +4199,6 @@ public class StatusBar extends SystemUI implements DemoMode,
* Plays the animation when an activity that was occluding Keyguard goes away.
*/
public void animateKeyguardUnoccluding() {
- mScrimController.animateKeyguardUnoccluding(500);
mNotificationPanel.setExpandedFraction(0f);
animateExpandNotificationsPanel();
}
@@ -4320,11 +4386,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mAmbientIndicationContainer.setVisibility(View.INVISIBLE);
}
}
- if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
- mScrimController.setKeyguardShowing(true);
- } else {
- mScrimController.setKeyguardShowing(false);
- }
mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
updateTheme();
updateDozingState();
@@ -4332,6 +4393,7 @@ public class StatusBar extends SystemUI implements DemoMode,
updateStackScrollerState(goingToFullShade, fromShadeLocked);
updateNotifications();
checkBarModes();
+ updateScrimController();
updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
mUnlockMethodCache.isMethodSecure(),
@@ -4369,7 +4431,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mContext.getThemeResId() != themeResId) {
mContext.setTheme(themeResId);
if (inflated) {
- reinflateViews();
+ onThemeChanged();
}
}
@@ -4395,11 +4457,10 @@ public class StatusBar extends SystemUI implements DemoMode,
boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup();
mNotificationPanel.setDozing(mDozing, animate);
mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
- mScrimController.setDozing(mDozing);
+ mDozeScrimController.setDozing(mDozing);
mKeyguardIndicationController.setDozing(mDozing);
mNotificationPanel.setDark(mDozing, animate);
updateQsExpansionEnabled();
- mDozeScrimController.setDozing(mDozing, animate);
updateRowStates();
Trace.endSection();
}
@@ -4894,6 +4955,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing);
updateHideIconsForBouncer(true /* animate */);
recomputeDisableFlags(true /* animate */);
+ updateScrimController();
}
public void cancelCurrentTouch() {
@@ -4945,12 +5007,10 @@ public class StatusBar extends SystemUI implements DemoMode,
mStackScroller.setAnimationsEnabled(true);
mVisualStabilityManager.setScreenOn(true);
mNotificationPanel.setTouchDisabled(false);
-
- maybePrepareWakeUpFromAod();
-
mDozeServiceHost.stopDozing();
updateVisibleToUser();
updateIsKeyguard();
+ updateScrimController();
}
};
@@ -4960,18 +5020,16 @@ public class StatusBar extends SystemUI implements DemoMode,
mFalsingManager.onScreenTurningOn();
mNotificationPanel.onScreenTurningOn();
- maybePrepareWakeUpFromAod();
-
if (mLaunchCameraOnScreenTurningOn) {
mNotificationPanel.launchCamera(false, mLastCameraLaunchSource);
mLaunchCameraOnScreenTurningOn = false;
}
+
+ updateScrimController();
}
@Override
public void onScreenTurnedOn() {
- mScrimController.wakeUpFromAod();
- mDozeScrimController.onScreenTurnedOn();
}
@Override
@@ -4989,13 +5047,6 @@ public class StatusBar extends SystemUI implements DemoMode,
return mWakefulnessLifecycle.getWakefulness();
}
- private void maybePrepareWakeUpFromAod() {
- int wakefulness = mWakefulnessLifecycle.getWakefulness();
- if (mDozing && wakefulness == WAKEFULNESS_WAKING && !isPulsing()) {
- mScrimController.prepareWakeUpFromAod();
- }
- }
-
private void vibrateForCameraGesture() {
// Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */);
@@ -5078,12 +5129,12 @@ public class StatusBar extends SystemUI implements DemoMode,
if (!mDeviceInteractive) {
// Avoid flickering of the scrim when we instant launch the camera and the bouncer
// comes on.
- mScrimController.dontAnimateBouncerChangesUntilNextFrame();
mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
}
if (isScreenTurningOnOrOn()) {
if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera");
mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
+ updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
// we will dismiss us too early since we are waiting on an activity to be drawn and
@@ -5125,15 +5176,16 @@ public class StatusBar extends SystemUI implements DemoMode,
private void updateDozing() {
Trace.beginSection("StatusBar#updateDozing");
// When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
- mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
+ boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
|| mFingerprintUnlockController.getMode()
== FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
// When in wake-and-unlock we may not have received a change to mState
// but we still should not be dozing, manually set to false.
if (mFingerprintUnlockController.getMode() ==
FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
- mDozing = false;
+ dozing = false;
}
+ mDozing = dozing;
mStatusBarWindowManager.setDozing(mDozing);
mStatusBarKeyguardViewManager.setDozing(mDozing);
if (mAmbientIndicationContainer instanceof DozeReceiver) {
@@ -5143,6 +5195,24 @@ public class StatusBar extends SystemUI implements DemoMode,
Trace.endSection();
}
+ public void updateScrimController() {
+ if (mBouncerShowing) {
+ mScrimController.transitionTo(ScrimState.BOUNCER);
+ } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) {
+ mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+ } else if (mBrightnessMirrorVisible) {
+ mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ } else if (isPulsing()) {
+ // Handled in DozeScrimController#setPulsing
+ } else if (mDozing) {
+ mScrimController.transitionTo(ScrimState.AOD);
+ } else if (mIsKeyguard) {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ } else {
+ mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+ }
+ }
+
public boolean isKeyguardShowing() {
if (mStatusBarKeyguardViewManager == null) {
Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true");
@@ -5202,7 +5272,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
mDozeScrimController.pulse(new PulseCallback() {
-
@Override
public void onPulseStarted() {
callback.onPulseStarted();
@@ -5287,11 +5356,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
@Override
- public void abortPulsing() {
- mDozeScrimController.abortPulsing();
- }
-
- @Override
public void extendPulse() {
mDozeScrimController.extendPulse();
}
@@ -5327,7 +5391,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void setAodDimmingScrim(float scrimOpacity) {
- mDozeScrimController.setAodDimmingScrim(scrimOpacity);
+ ScrimState.AOD.setAodFrontScrimAlpha(scrimOpacity);
}
public void dispatchDoubleTap(float viewX, float viewY) {
diff --git a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 09828dcd..ef05bbb4 100644
--- a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -24,7 +24,6 @@ import android.content.ComponentCallbacks2;
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
-import android.os.Trace;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -71,17 +70,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
protected final Context mContext;
private final StatusBarWindowManager mStatusBarWindowManager;
- private final boolean mDisplayBlanksAfterDoze;
protected LockPatternUtils mLockPatternUtils;
protected ViewMediatorCallback mViewMediatorCallback;
protected StatusBar mStatusBar;
- private ScrimController mScrimController;
private FingerprintUnlockController mFingerprintUnlockController;
private ViewGroup mContainer;
- private boolean mScreenTurnedOn;
protected KeyguardBouncer mBouncer;
protected boolean mShowing;
protected boolean mOccluded;
@@ -95,12 +91,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private boolean mLastBouncerDismissible;
protected boolean mLastRemoteInputActive;
private boolean mLastDozing;
- private boolean mLastDeferScrimFadeOut;
private int mLastFpMode;
private OnDismissAction mAfterKeyguardGoneAction;
private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
- private boolean mDeferScrimFadeOut;
// Dismiss action to be launched when we stop dozing or the keyguard is gone.
private DismissWithActionRequest mPendingWakeupAction;
@@ -125,18 +119,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mLockPatternUtils = lockPatternUtils;
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
- mDisplayBlanksAfterDoze = context.getResources().getBoolean(
- com.android.internal.R.bool.config_displayBlanksAfterDoze);
}
public void registerStatusBar(StatusBar statusBar,
ViewGroup container,
- ScrimController scrimController,
FingerprintUnlockController fingerprintUnlockController,
DismissCallbackRegistry dismissCallbackRegistry) {
mStatusBar = statusBar;
mContainer = container;
- mScrimController = scrimController;
mFingerprintUnlockController = fingerprintUnlockController;
mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry);
@@ -149,7 +139,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
public void show(Bundle options) {
mShowing = true;
mStatusBarWindowManager.setKeyguardShowing(true);
- mScrimController.abortKeyguardFadingOut();
reset(true /* hideBouncerWhenShowing */);
}
@@ -253,15 +242,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public void onScreenTurnedOn() {
- Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn");
- mScreenTurnedOn = true;
- if (mDeferScrimFadeOut) {
- mDeferScrimFadeOut = false;
- animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
- true /* skipFirstFrame */);
- updateStates();
- }
- Trace.endSection();
+ // TODO: remove
}
@Override
@@ -285,7 +266,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public void onScreenTurnedOff() {
- mScreenTurnedOn = false;
+ // TODO: remove
}
public void notifyDeviceWakeUpRequested() {
@@ -374,10 +355,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mStatusBarWindowManager.setKeyguardFadingAway(true);
hideBouncer(true /* destroyView */);
updateStates();
- mScrimController.animateKeyguardFadingOut(
- StatusBar.FADE_KEYGUARD_START_DELAY,
- StatusBar.FADE_KEYGUARD_DURATION, null,
- false /* skipFirstFrame */);
}
}, new Runnable() {
@Override
@@ -400,36 +377,16 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mFingerprintUnlockController.startKeyguardFadingAway();
hideBouncer(true /* destroyView */);
if (wakeUnlockPulsing) {
- mStatusBarWindowManager.setKeyguardFadingAway(true);
mStatusBar.fadeKeyguardWhilePulsing();
- animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
- mStatusBar::hideKeyguard, false /* skipFirstFrame */);
+ wakeAndUnlockDejank();
} else {
mFingerprintUnlockController.startKeyguardFadingAway();
mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
boolean staying = mStatusBar.hideKeyguard();
if (!staying) {
mStatusBarWindowManager.setKeyguardFadingAway(true);
- if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
- boolean turnedOnSinceAuth =
- mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating();
- if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) {
- // Not ready to animate yet; either because the screen is not on yet,
- // or it is on but will turn off before waking out of doze.
- mDeferScrimFadeOut = true;
- } else {
-
- // Screen is already on, don't defer with fading out.
- animateScrimControllerKeyguardFadingOut(0,
- WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
- true /* skipFirstFrame */);
- }
- } else {
- animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
- false /* skipFirstFrame */);
- }
+ wakeAndUnlockDejank();
} else {
- mScrimController.animateGoingToFullShade(delay, fadeoutDuration);
mStatusBar.finishKeyguardFadingAway();
mFingerprintUnlockController.finishKeyguardFadingAway();
}
@@ -444,35 +401,22 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
hideBouncer(true /* destroyView */);
}
- public void onOverlayChanged() {
+ public void onThemeChanged() {
hideBouncer(true /* destroyView */);
mBouncer.prepare();
}
- private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
- boolean skipFirstFrame) {
- animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */,
- skipFirstFrame);
+ public void onKeyguardFadedAway() {
+ mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
+ 100);
+ mStatusBar.finishKeyguardFadingAway();
+ mFingerprintUnlockController.finishKeyguardFadingAway();
+ WindowManagerGlobal.getInstance().trimMemory(
+ ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+
}
- private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
- final Runnable endRunnable, boolean skipFirstFrame) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0);
- mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() {
- @Override
- public void run() {
- if (endRunnable != null) {
- endRunnable.run();
- }
- mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
- 100);
- mStatusBar.finishKeyguardFadingAway();
- mFingerprintUnlockController.finishKeyguardFadingAway();
- WindowManagerGlobal.getInstance().trimMemory(
- ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0);
- }
- }, skipFirstFrame);
+ private void wakeAndUnlockDejank() {
if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
&& LatencyTracker.isEnabled(mContext)) {
DejankUtils.postAfterTraversal(() ->
@@ -593,7 +537,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
mStatusBarWindowManager.setBouncerShowing(bouncerShowing);
mStatusBar.setBouncerShowing(bouncerShowing);
- mScrimController.setBouncerShowing(bouncerShowing);
}
KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
@@ -611,7 +554,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mLastBouncerDismissible = bouncerDismissible;
mLastRemoteInputActive = remoteInputActive;
mLastDozing = mDozing;
- mLastDeferScrimFadeOut = mDeferScrimFadeOut;
mLastFpMode = mFingerprintUnlockController.getMode();
mStatusBar.onKeyguardViewManagerStatesUpdated();
}
@@ -624,7 +566,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
boolean keyguardShowing = mShowing && !mOccluded;
boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING;
return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
- || mRemoteInputActive) && !mDeferScrimFadeOut;
+ || mRemoteInputActive);
}
/**
@@ -634,7 +576,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
boolean keyguardShowing = mLastShowing && !mLastOccluded;
boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING;
return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
- || mLastRemoteInputActive) && !mLastDeferScrimFadeOut;
+ || mLastRemoteInputActive);
}
public boolean shouldDismissOnMenuPressed() {
diff --git a/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 42ce4c5b..a011952f 100644
--- a/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
import android.util.ArraySet;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewPropertyAnimator;
@@ -25,55 +27,52 @@ import android.widget.FrameLayout;
import com.android.internal.util.Preconditions;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import java.util.function.Consumer;
+
/**
* Controls showing and hiding of the brightness mirror.
*/
public class BrightnessMirrorController
implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
- private final NotificationStackScrollLayout mStackScroller;
- public long TRANSITION_DURATION_OUT = 150;
- public long TRANSITION_DURATION_IN = 200;
+ private final static long TRANSITION_DURATION_OUT = 150;
+ private final static long TRANSITION_DURATION_IN = 200;
private final StatusBarWindowView mStatusBarWindow;
- private final ScrimController mScrimController;
+ private final NotificationStackScrollLayout mStackScroller;
+ private final Consumer<Boolean> mVisibilityCallback;
private final View mNotificationPanel;
private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
private final int[] mInt2Cache = new int[2];
private View mBrightnessMirror;
public BrightnessMirrorController(StatusBarWindowView statusBarWindow,
- ScrimController scrimController) {
+ @NonNull Consumer<Boolean> visibilityCallback) {
mStatusBarWindow = statusBarWindow;
mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel);
- mStackScroller = (NotificationStackScrollLayout) statusBarWindow.findViewById(
- R.id.notification_stack_scroller);
- mScrimController = scrimController;
+ mStackScroller = statusBarWindow.findViewById(R.id.notification_stack_scroller);
+ mVisibilityCallback = visibilityCallback;
}
public void showMirror() {
mBrightnessMirror.setVisibility(View.VISIBLE);
mStackScroller.setFadingOut(true);
- mScrimController.forceHideScrims(true /* hide */, true /* animated */);
+ mVisibilityCallback.accept(true);
outAnimation(mNotificationPanel.animate())
.withLayer();
}
public void hideMirror() {
- mScrimController.forceHideScrims(false /* hide */, true /* animated */);
+ mVisibilityCallback.accept(false);
inAnimation(mNotificationPanel.animate())
.withLayer()
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mBrightnessMirror.setVisibility(View.INVISIBLE);
- mStackScroller.setFadingOut(false);
- }
+ .withEndAction(() -> {
+ mBrightnessMirror.setVisibility(View.INVISIBLE);
+ mStackScroller.setFadingOut(false);
});
}
@@ -128,9 +127,11 @@ public class BrightnessMirrorController
}
private void reinflate() {
+ ContextThemeWrapper qsThemeContext =
+ new ContextThemeWrapper(mBrightnessMirror.getContext(), R.style.qs_theme);
int index = mStatusBarWindow.indexOfChild(mBrightnessMirror);
mStatusBarWindow.removeView(mBrightnessMirror);
- mBrightnessMirror = LayoutInflater.from(mBrightnessMirror.getContext()).inflate(
+ mBrightnessMirror = LayoutInflater.from(qsThemeContext).inflate(
R.layout.brightness_mirror, mStatusBarWindow, false);
mStatusBarWindow.addView(mBrightnessMirror, index);
diff --git a/com/android/systemui/statusbar/policy/RemoteInputView.java b/com/android/systemui/statusbar/policy/RemoteInputView.java
index 37b0de40..4fc50442 100644
--- a/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -53,11 +53,9 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
-import com.android.systemui.statusbar.stack.ScrollContainer;
import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
@@ -82,8 +80,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private NotificationData.Entry mEntry;
- private ScrollContainer mScrollContainer;
- private View mScrollContainerChild;
private boolean mRemoved;
private int mRevealCx;
@@ -347,41 +343,16 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- findScrollContainer();
- if (mScrollContainer != null) {
- mScrollContainer.requestDisallowLongPress();
- mScrollContainer.requestDisallowDismiss();
- }
+ mController.requestDisallowLongPressAndDismiss();
}
return super.onInterceptTouchEvent(ev);
}
public boolean requestScrollTo() {
- findScrollContainer();
- mScrollContainer.lockScrollTo(mScrollContainerChild);
+ mController.lockScrollTo(mEntry);
return true;
}
- private void findScrollContainer() {
- if (mScrollContainer == null) {
- mScrollContainerChild = null;
- ViewParent p = this;
- while (p != null) {
- if (mScrollContainerChild == null && p instanceof ExpandableView) {
- mScrollContainerChild = (View) p;
- }
- if (p.getParent() instanceof ScrollContainer) {
- mScrollContainer = (ScrollContainer) p.getParent();
- if (mScrollContainerChild == null) {
- mScrollContainerChild = (View) p;
- }
- break;
- }
- p = p.getParent();
- }
- }
- }
-
public boolean isActive() {
return mEditText.isFocused() && mEditText.isEnabled();
}
diff --git a/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 527addf1..f5ae88b1 100644
--- a/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -154,7 +154,9 @@ public class UserInfoControllerImpl implements UserInfoController {
avatar = new UserIconDrawable(avatarSize)
.setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
} else {
- avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
+ avatar = UserIcons.getDefaultUserIcon(
+ context.getResources(),
+ isGuest? UserHandle.USER_NULL : userId,
lightIcon);
}
diff --git a/com/android/systemui/statusbar/policy/UserSwitcherController.java b/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 700c01a5..7006d389 100644
--- a/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -747,7 +747,8 @@ public class UserSwitcherController {
if (item.isAddUser) {
return context.getDrawable(R.drawable.ic_add_circle_qs);
}
- Drawable icon = UserIcons.getDefaultUserIcon(item.resolveId(), /* light= */ false);
+ Drawable icon = UserIcons.getDefaultUserIcon(
+ context.getResources(), item.resolveId(), /* light= */ false);
if (item.isGuest) {
icon.setColorFilter(Utils.getColorAttr(context, android.R.attr.colorForeground),
Mode.SRC_IN);
@@ -910,6 +911,7 @@ public class UserSwitcherController {
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(R.string.guest_exit_guest_dialog_remove), this);
+ SystemUIDialog.setWindowOnTop(this);
setCanceledOnTouchOutside(false);
mGuestId = guestId;
mTargetId = targetId;
@@ -937,6 +939,7 @@ public class UserSwitcherController {
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(android.R.string.ok), this);
+ SystemUIDialog.setWindowOnTop(this);
}
@Override
@@ -957,7 +960,7 @@ public class UserSwitcherController {
}
int id = user.id;
Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
- id, /* light= */ false));
+ mContext.getResources(), id, /* light= */ false));
mUserManager.setUserIcon(id, icon);
switchToUserId(id);
}
diff --git a/com/android/systemui/statusbar/stack/AnimationProperties.java b/com/android/systemui/statusbar/stack/AnimationProperties.java
index ebb0a6d2..2f6e6584 100644
--- a/com/android/systemui/statusbar/stack/AnimationProperties.java
+++ b/com/android/systemui/statusbar/stack/AnimationProperties.java
@@ -36,7 +36,12 @@ public class AnimationProperties {
* @return an animation filter for this animation.
*/
public AnimationFilter getAnimationFilter() {
- return new AnimationFilter();
+ return new AnimationFilter() {
+ @Override
+ public boolean shouldAnimateProperty(Property property) {
+ return true;
+ }
+ };
}
/**
diff --git a/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index fe53104f..c0241e36 100644
--- a/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -1260,4 +1260,17 @@ public class NotificationChildrenContainer extends ViewGroup {
public boolean isUserLocked() {
return mUserLocked;
}
+
+ public void setCurrentBottomRoundness(float currentBottomRoundness) {
+ boolean last = true;
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ float bottomRoundness = last ? currentBottomRoundness : 0.0f;
+ child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
+ last = false;
+ }
+ }
}
diff --git a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index efe049ab..ebebfacf 100644
--- a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -30,6 +30,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
@@ -75,6 +76,7 @@ import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableOutlineView;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationGuts;
@@ -82,8 +84,10 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationSnooze;
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -108,8 +112,7 @@ import java.util.List;
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
- NotificationMenuRowPlugin.OnMenuEventListener, ScrollContainer,
- VisibilityLocationProvider {
+ NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider {
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
@@ -121,12 +124,23 @@ public class NotificationStackScrollLayout extends ViewGroup
* Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
+ private static final AnimatableProperty SIDE_PADDINGS = AnimatableProperty.from(
+ "sidePaddings",
+ NotificationStackScrollLayout::setCurrentSidePadding,
+ NotificationStackScrollLayout::getCurrentSidePadding,
+ R.id.side_padding_animator_tag,
+ R.id.side_padding_animator_end_tag,
+ R.id.side_padding_animator_start_tag);
+ private static final AnimationProperties SIDE_PADDING_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
private ExpandHelper mExpandHelper;
private NotificationSwipeHelper mSwipeHelper;
private boolean mSwipingInProgress;
private int mCurrentStackHeight = Integer.MAX_VALUE;
private final Paint mBackgroundPaint = new Paint();
+ private final Path mBackgroundPath = new Path();
+ private final float[] mBackgroundRadii = new float[8];
private final boolean mShouldDrawNotificationBackground;
private float mExpandedHeight;
@@ -157,6 +171,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private int mTopPadding;
private int mBottomMargin;
private int mBottomInset = 0;
+ private float mCurrentSidePadding;
/**
* The algorithm which calculates the properties for our children
@@ -383,6 +398,9 @@ public class NotificationStackScrollLayout extends ViewGroup
private int mCachedBackgroundColor;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
private Runnable mAnimateScroll = this::animateScroll;
+ private int mCornerRadius;
+ private int mLockscreenSidePaddings;
+ private int mSidePaddings;
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -419,6 +437,8 @@ public class NotificationStackScrollLayout extends ViewGroup
res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
updateWillNotDraw();
+ mBackgroundPaint.setAntiAlias(true);
+ mBackgroundPaint.setStyle(Paint.Style.FILL);
if (DEBUG) {
mDebugPaint = new Paint();
mDebugPaint.setColor(0xffff0000);
@@ -466,8 +486,7 @@ public class NotificationStackScrollLayout extends ViewGroup
protected void onDraw(Canvas canvas) {
if (mShouldDrawNotificationBackground && !mAmbientState.isDark()
&& mCurrentBounds.top < mCurrentBounds.bottom) {
- canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
- mBackgroundPaint);
+ canvas.drawPath(mBackgroundPath, mBackgroundPaint);
}
if (DEBUG) {
@@ -520,8 +539,12 @@ public class NotificationStackScrollLayout extends ViewGroup
R.dimen.min_top_overscroll_to_qs);
mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+ mLockscreenSidePaddings = res.getDimensionPixelSize(
+ R.dimen.notification_lockscreen_side_paddings);
mMinInteractionHeight = res.getDimensionPixelSize(
R.dimen.notification_min_interaction_height);
+ mCornerRadius = res.getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
}
public void setDrawBackgroundAsSrc(boolean asSrc) {
@@ -1195,7 +1218,6 @@ public class NotificationStackScrollLayout extends ViewGroup
mScrollingEnabled = enable;
}
- @Override
public void lockScrollTo(View v) {
if (mForcedScroll == v) {
return;
@@ -1204,7 +1226,6 @@ public class NotificationStackScrollLayout extends ViewGroup
scrollTo(v);
}
- @Override
public boolean scrollTo(View v) {
ExpandableView expandableView = (ExpandableView) v;
int positionInLinearLayout = getPositionInLinearLayout(v);
@@ -2221,9 +2242,31 @@ public class NotificationStackScrollLayout extends ViewGroup
mScrimController.setExcludedBackgroundArea(
mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
: mCurrentBounds);
+ updateBackgroundPath();
invalidate();
}
+ private void updateBackgroundPath() {
+ mBackgroundPath.reset();
+ float topRoundness = 0;
+ if (mFirstVisibleBackgroundChild != null) {
+ topRoundness = mFirstVisibleBackgroundChild.getCurrentBackgroundRadiusTop();
+ }
+ topRoundness = onKeyguard() ? mCornerRadius : topRoundness;
+ float bottomRoundNess = mCornerRadius;
+ mBackgroundRadii[0] = topRoundness;
+ mBackgroundRadii[1] = topRoundness;
+ mBackgroundRadii[2] = topRoundness;
+ mBackgroundRadii[3] = topRoundness;
+ mBackgroundRadii[4] = bottomRoundNess;
+ mBackgroundRadii[5] = bottomRoundNess;
+ mBackgroundRadii[6] = bottomRoundNess;
+ mBackgroundRadii[7] = bottomRoundNess;
+ mBackgroundPath.addRoundRect(mCurrentSidePadding, mCurrentBounds.top,
+ getWidth() - mCurrentSidePadding, mCurrentBounds.bottom, mBackgroundRadii,
+ Path.Direction.CCW);
+ }
+
/**
* Update the background bounds to the new desired bounds
*/
@@ -2236,6 +2279,8 @@ public class NotificationStackScrollLayout extends ViewGroup
mBackgroundBounds.left = mTempInt2[0];
mBackgroundBounds.right = mTempInt2[0] + getWidth();
}
+ mBackgroundBounds.left += mCurrentSidePadding;
+ mBackgroundBounds.right -= mCurrentSidePadding;
if (!mIsExpanded) {
mBackgroundBounds.top = 0;
mBackgroundBounds.bottom = 0;
@@ -2820,16 +2865,45 @@ public class NotificationStackScrollLayout extends ViewGroup
private void updateFirstAndLastBackgroundViews() {
ActivatableNotificationView firstChild = getFirstChildWithBackground();
ActivatableNotificationView lastChild = getLastChildWithBackground();
+ boolean firstChanged = firstChild != mFirstVisibleBackgroundChild;
+ boolean lastChanged = lastChild != mLastVisibleBackgroundChild;
if (mAnimationsEnabled && mIsExpanded) {
- mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
- mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
+ mAnimateNextBackgroundTop = firstChanged;
+ mAnimateNextBackgroundBottom = lastChanged;
} else {
mAnimateNextBackgroundTop = false;
mAnimateNextBackgroundBottom = false;
}
+ if (firstChanged && mFirstVisibleBackgroundChild != null
+ && !mFirstVisibleBackgroundChild.isRemoved()) {
+ mFirstVisibleBackgroundChild.setTopRoundness(0.0f,
+ mFirstVisibleBackgroundChild.isShown());
+ }
+ if (lastChanged && mLastVisibleBackgroundChild != null
+ && !mLastVisibleBackgroundChild.isRemoved()) {
+ mLastVisibleBackgroundChild.setBottomRoundness(0.0f,
+ mLastVisibleBackgroundChild.isShown());
+ }
mFirstVisibleBackgroundChild = firstChild;
mLastVisibleBackgroundChild = lastChild;
mAmbientState.setLastVisibleBackgroundChild(lastChild);
+ applyRoundedNess();
+ }
+
+ private void applyRoundedNess() {
+ if (mFirstVisibleBackgroundChild != null) {
+ mFirstVisibleBackgroundChild.setTopRoundness(
+ mStatusBarState == StatusBarState.KEYGUARD ? 1.0f : 0.0f,
+ mFirstVisibleBackgroundChild.isShown()
+ && !mChildrenToAddAnimated.contains(mFirstVisibleBackgroundChild));
+ }
+ if (mLastVisibleBackgroundChild != null) {
+ mLastVisibleBackgroundChild.setBottomRoundness(1.0f,
+ mLastVisibleBackgroundChild.isShown()
+ && !mChildrenToAddAnimated.contains(mLastVisibleBackgroundChild));
+ }
+ updateBackgroundPath();
+ invalidate();
}
private void onViewAddedInternal(View child) {
@@ -2838,6 +2912,7 @@ public class NotificationStackScrollLayout extends ViewGroup
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
updateChronometerForChild(child);
+ updateCurrentSidePaddings(child);
}
private void updateHideSensitiveForChild(View child) {
@@ -3321,12 +3396,10 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
- @Override
public void requestDisallowLongPress() {
cancelLongPress();
}
- @Override
public void requestDisallowDismiss() {
mDisallowDismissInThisMotion = true;
}
@@ -4285,6 +4358,43 @@ public class NotificationStackScrollLayout extends ViewGroup
public void setStatusBarState(int statusBarState) {
mStatusBarState = statusBarState;
mAmbientState.setStatusBarState(statusBarState);
+ applyRoundedNess();
+ updateSidePaddings();
+ }
+
+ private void updateSidePaddings() {
+ int sidePaddings = mStatusBarState == StatusBarState.KEYGUARD ? mLockscreenSidePaddings : 0;
+ if (sidePaddings != mSidePaddings) {
+ boolean animate = isShown();
+ mSidePaddings = sidePaddings;
+ PropertyAnimator.setProperty(this, SIDE_PADDINGS, sidePaddings,
+ SIDE_PADDING_PROPERTIES, animate);
+ }
+ }
+
+ protected void setCurrentSidePadding(float sidePadding) {
+ mCurrentSidePadding = sidePadding;
+ updateBackground();
+ applySidePaddingsToChildren();
+ }
+
+ private void applySidePaddingsToChildren() {
+ for (int i = 0; i < getChildCount(); i++) {
+ View view = getChildAt(i);
+ updateCurrentSidePaddings(view);
+ }
+ }
+
+ private void updateCurrentSidePaddings(View view) {
+ if (!(view instanceof ExpandableOutlineView)) {
+ return;
+ }
+ ExpandableOutlineView outlineView = (ExpandableOutlineView) view;
+ outlineView.setCurrentSidePaddings(mCurrentSidePadding);
+ }
+
+ protected float getCurrentSidePadding() {
+ return mCurrentSidePadding;
}
public void setExpandingVelocity(float expandingVelocity) {
diff --git a/com/android/systemui/statusbar/stack/ScrollContainer.java b/com/android/systemui/statusbar/stack/ScrollContainer.java
deleted file mode 100644
index b9d12ce8..00000000
--- a/com/android/systemui/statusbar/stack/ScrollContainer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.stack;
-
-import android.view.View;
-
-/**
- * Interface for container layouts that scroll and listen for long presses. A child that
- * wants to handle long press can use this to cancel the parents long press logic or request
- * to be made visible by scrolling to it.
- */
-public interface ScrollContainer {
- /**
- * Request that the view does not perform long press for the current touch.
- */
- void requestDisallowLongPress();
-
- /**
- * Request that the view is made visible by scrolling to it.
- * Return true if it scrolls.
- */
- boolean scrollTo(View v);
-
- /**
- * Like {@link #scrollTo(View)}, but keeps the scroll locked until the user
- * scrolls, or {@param v} loses focus or is detached.
- */
- void lockScrollTo(View v);
-
- /**
- * Request that the view does not dismiss for the current touch.
- */
- void requestDisallowDismiss();
-}
diff --git a/com/android/systemui/statusbar/stack/ViewState.java b/com/android/systemui/statusbar/stack/ViewState.java
index 27b730cd..682b8493 100644
--- a/com/android/systemui/statusbar/stack/ViewState.java
+++ b/com/android/systemui/statusbar/stack/ViewState.java
@@ -21,7 +21,6 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
-import android.app.Notification;
import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
@@ -29,7 +28,7 @@ import android.view.animation.Interpolator;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -64,8 +63,8 @@ public class ViewState {
private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
- private static final PropertyAnimator.AnimatableProperty SCALE_X_PROPERTY
- = new PropertyAnimator.AnimatableProperty() {
+ private static final AnimatableProperty SCALE_X_PROPERTY
+ = new AnimatableProperty() {
@Override
public int getAnimationStartTag() {
@@ -88,8 +87,8 @@ public class ViewState {
}
};
- private static final PropertyAnimator.AnimatableProperty SCALE_Y_PROPERTY
- = new PropertyAnimator.AnimatableProperty() {
+ private static final AnimatableProperty SCALE_Y_PROPERTY
+ = new AnimatableProperty() {
@Override
public int getAnimationStartTag() {
@@ -251,7 +250,7 @@ public class ViewState {
return getChildTag(view, tag) != null;
}
- public static boolean isAnimating(View view, PropertyAnimator.AnimatableProperty property) {
+ public static boolean isAnimating(View view, AnimatableProperty property) {
return getChildTag(view, property.getAnimatorTag()) != null;
}
@@ -403,7 +402,7 @@ public class ViewState {
startZTranslationAnimation(view, NO_NEW_ANIMATIONS);
}
- private void updateAnimation(View view, PropertyAnimator.AnimatableProperty property,
+ private void updateAnimation(View view, AnimatableProperty property,
float endValue) {
PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS);
}
diff --git a/com/android/systemui/usb/UsbPermissionActivity.java b/com/android/systemui/usb/UsbPermissionActivity.java
index 87d11b24..4606aee3 100644
--- a/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/com/android/systemui/usb/UsbPermissionActivity.java
@@ -235,7 +235,7 @@ public class UsbPermissionActivity extends AlertActivity
intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
if (mPermissionGranted) {
service.grantDevicePermission(mDevice, mUid);
- if (mAlwaysUse.isChecked()) {
+ if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
final int userId = UserHandle.getUserId(mUid);
service.setDevicePackage(mDevice, mPackageName, userId);
}
@@ -245,7 +245,7 @@ public class UsbPermissionActivity extends AlertActivity
intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
if (mPermissionGranted) {
service.grantAccessoryPermission(mAccessory, mUid);
- if (mAlwaysUse.isChecked()) {
+ if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
final int userId = UserHandle.getUserId(mUid);
service.setAccessoryPackage(mAccessory, mPackageName, userId);
}
diff --git a/com/android/systemui/volume/VolumeDialogImpl.java b/com/android/systemui/volume/VolumeDialogImpl.java
index 4b8f581a..383d3276 100644
--- a/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/com/android/systemui/volume/VolumeDialogImpl.java
@@ -207,6 +207,7 @@ public class VolumeDialogImpl implements VolumeDialog {
} else {
addExistingRows();
}
+ updateRowsH(getActiveRow());
}
private ColorStateList loadColorStateList(int colorResId) {
@@ -440,7 +441,8 @@ public class VolumeDialogImpl implements VolumeDialog {
.withEndAction(() -> mDialog.dismiss())
.setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
.start();
- if (mAccessibilityMgr.isEnabled()) {
+ if (mAccessibilityMgr.isObservedEventType(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)) {
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
event.setPackageName(mContext.getPackageName());
diff --git a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
index 3d5476d0..7c9aeded 100644
--- a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
+++ b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open 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,55 +16,24 @@
package com.android.uiautomator.testrunner;
-import android.content.Context;
+import android.app.Instrumentation;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
-import android.view.inputmethod.InputMethodInfo;
+import android.test.InstrumentationTestCase;
-import com.android.internal.view.IInputMethodManager;
+import com.android.uiautomator.core.InstrumentationUiAutomatorBridge;
import com.android.uiautomator.core.UiDevice;
-import junit.framework.TestCase;
-
-import java.util.List;
-
/**
- * UI automation test should extend this class. This class provides access
- * to the following:
- * {@link UiDevice} instance
- * {@link Bundle} for command line parameters.
- * @since API Level 16
+ * UI Automator test case that is executed on the device.
* @deprecated New tests should be written using UI Automator 2.0 which is available as part of the
* Android Testing Support Library.
*/
@Deprecated
-public class UiAutomatorTestCase extends TestCase {
+public class UiAutomatorTestCase extends InstrumentationTestCase {
- private static final String DISABLE_IME = "disable_ime";
- private static final String DUMMY_IME_PACKAGE = "com.android.testing.dummyime";
- private UiDevice mUiDevice;
private Bundle mParams;
private IAutomationSupport mAutomationSupport;
- private boolean mShouldDisableIme = false;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mShouldDisableIme = "true".equals(mParams.getString(DISABLE_IME));
- if (mShouldDisableIme) {
- setDummyIme();
- }
- }
-
- @Override
- protected void tearDown() throws Exception {
- if (mShouldDisableIme) {
- restoreActiveIme();
- }
- super.tearDown();
- }
/**
* Get current instance of {@link UiDevice}. Works similar to calling the static
@@ -72,7 +41,7 @@ public class UiAutomatorTestCase extends TestCase {
* @since API Level 16
*/
public UiDevice getUiDevice() {
- return mUiDevice;
+ return UiDevice.getInstance();
}
/**
@@ -85,34 +54,43 @@ public class UiAutomatorTestCase extends TestCase {
return mParams;
}
+ void setAutomationSupport(IAutomationSupport automationSupport) {
+ mAutomationSupport = automationSupport;
+ }
+
/**
* Provides support for running tests to report interim status
*
* @return IAutomationSupport
* @since API Level 16
+ * @deprecated Use {@link Instrumentation#sendStatus(int, Bundle)} instead
*/
public IAutomationSupport getAutomationSupport() {
+ if (mAutomationSupport == null) {
+ mAutomationSupport = new InstrumentationAutomationSupport(getInstrumentation());
+ }
return mAutomationSupport;
}
/**
- * package private
- * @param uiDevice
- */
- void setUiDevice(UiDevice uiDevice) {
- mUiDevice = uiDevice;
- }
-
- /**
- * package private
- * @param params
+ * Initializes this test case.
+ *
+ * @param params Instrumentation arguments.
*/
- void setParams(Bundle params) {
+ void initialize(Bundle params) {
mParams = params;
- }
- void setAutomationSupport(IAutomationSupport automationSupport) {
- mAutomationSupport = automationSupport;
+ // check if this is a monkey test mode
+ String monkeyVal = mParams.getString("monkey");
+ if (monkeyVal != null) {
+ // only if the monkey key is specified, we alter the state of monkey
+ // else we should leave things as they are.
+ getInstrumentation().getUiAutomation().setRunAsMonkey(Boolean.valueOf(monkeyVal));
+ }
+
+ UiDevice.getInstance().initialize(new InstrumentationUiAutomatorBridge(
+ getInstrumentation().getContext(),
+ getInstrumentation().getUiAutomation()));
}
/**
@@ -123,28 +101,4 @@ public class UiAutomatorTestCase extends TestCase {
public void sleep(long ms) {
SystemClock.sleep(ms);
}
-
- private void setDummyIme() throws RemoteException {
- IInputMethodManager im = IInputMethodManager.Stub.asInterface(ServiceManager
- .getService(Context.INPUT_METHOD_SERVICE));
- List<InputMethodInfo> infos = im.getInputMethodList();
- String id = null;
- for (InputMethodInfo info : infos) {
- if (DUMMY_IME_PACKAGE.equals(info.getComponent().getPackageName())) {
- id = info.getId();
- }
- }
- if (id == null) {
- throw new RuntimeException(String.format(
- "Required testing fixture missing: IME package (%s)", DUMMY_IME_PACKAGE));
- }
- im.setInputMethod(null, id);
- }
-
- private void restoreActiveIme() throws RemoteException {
- // TODO: figure out a way to restore active IME
- // Currently retrieving active IME requires querying secure settings provider, which is hard
- // to do without a Context; so the caveat here is that to make the post test device usable,
- // the active IME needs to be manually switched.
- }
}
diff --git a/java/lang/Math.java b/java/lang/Math.java
index 3ce39d9f..5c5ef1cb 100644
--- a/java/lang/Math.java
+++ b/java/lang/Math.java
@@ -106,6 +106,9 @@ import sun.misc.DoubleConsts;
public final class Math {
+ // Android-changed: Numerous methods in this class are re-implemented in native for performance.
+ // Those methods are also annotated @FastNative.
+
/**
* Don't let anyone instantiate this class.
*/
@@ -754,6 +757,8 @@ public final class Math {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
+ // Android-added: setRandomSeedInternal(long), called after zygote forks.
+ // This allows different processes to have different random seeds.
/**
* Set the seed for the pseudo random generator used by {@link #random()}
* and {@link #randomIntInternal()}.
@@ -764,6 +769,7 @@ public final class Math {
RandomNumberGeneratorHolder.randomNumberGenerator.setSeed(seed);
}
+ // Android-added: randomIntInternal() method: like random() but for int.
/**
* @hide for internal use only.
*/
@@ -771,6 +777,7 @@ public final class Math {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextInt();
}
+ // Android-added: randomLongInternal() method: like random() but for long.
/**
* @hide for internal use only.
*/
@@ -1221,9 +1228,11 @@ public final class Math {
* @return the absolute value of the argument.
*/
public static float abs(float a) {
+ // Android-changed: Implementation modified to exactly match ART intrinsics behavior.
// Note, as a "quality of implementation", rather than pure "spec compliance",
// we require that Math.abs() clears the sign bit (but changes nothing else)
// for all numbers, including NaN (signaling NaN may become quiet though).
+ // http://b/30758343
return Float.intBitsToFloat(0x7fffffff & Float.floatToRawIntBits(a));
}
@@ -1243,9 +1252,11 @@ public final class Math {
* @return the absolute value of the argument.
*/
public static double abs(double a) {
+ // Android-changed: Implementation modified to exactly match ART intrinsics behavior.
// Note, as a "quality of implementation", rather than pure "spec compliance",
// we require that Math.abs() clears the sign bit (but changes nothing else)
// for all numbers, including NaN (signaling NaN may become quiet though).
+ // http://b/30758343
return Double.longBitsToDouble(0x7fffffffffffffffL & Double.doubleToRawLongBits(a));
}
diff --git a/java/lang/NoClassDefFoundError.java b/java/lang/NoClassDefFoundError.java
index 869c8d9f..e4f764f5 100644
--- a/java/lang/NoClassDefFoundError.java
+++ b/java/lang/NoClassDefFoundError.java
@@ -60,6 +60,7 @@ class NoClassDefFoundError extends LinkageError {
super(s);
}
+ // Android-added: A new constructor for use by the Android runtime.
/**
* Constructs a new {@code NoClassDefFoundError} with the current stack
* trace, the specified detail message and the specified cause. Used
diff --git a/java/lang/Package.java b/java/lang/Package.java
index 55d2ba7d..33136f32 100644
--- a/java/lang/Package.java
+++ b/java/lang/Package.java
@@ -279,6 +279,8 @@ public class Package implements java.lang.reflect.AnnotatedElement {
*/
@CallerSensitive
public static Package getPackage(String name) {
+ // Android-changed: Use VMStack.getCallingClassLoader() to obtain the classloader.
+ // ClassLoader l = ClassLoader.getClassLoader(Reflection.getCallerClass());
ClassLoader l = VMStack.getCallingClassLoader();
if (l != null) {
return l.getPackage(name);
@@ -301,6 +303,8 @@ public class Package implements java.lang.reflect.AnnotatedElement {
*/
@CallerSensitive
public static Package[] getPackages() {
+ // Android-changed: Use VMStack.getCallingClassLoader() to obtain the classloader.
+ // ClassLoader l = ClassLoader.getClassLoader(Reflection.getCallerClass());
ClassLoader l = VMStack.getCallingClassLoader();
if (l != null) {
return l.getPackages();
@@ -358,7 +362,7 @@ public class Package implements java.lang.reflect.AnnotatedElement {
* @return the string representation of the package.
*/
public String toString() {
- // Android-changed start
+ // BEGIN Android-added: Backwards compatibility fix for target API <= 24.
// Several apps try to parse the output of toString(). This is a really
// bad idea - especially when there's a Package.getName() function as well as a
// Class.getName() function that can be used instead.
@@ -367,7 +371,7 @@ public class Package implements java.lang.reflect.AnnotatedElement {
if (targetSdkVersion > 0 && targetSdkVersion <= 24) {
return "package " + pkgName;
}
- // Android-changed end
+ // END Android-added: Backwards compatibility fix for target API <= 24.
String spec = specTitle;
String ver = specVersion;
diff --git a/java/lang/ProcessBuilder.java b/java/lang/ProcessBuilder.java
index 0c892c23..fc58abc3 100644
--- a/java/lang/ProcessBuilder.java
+++ b/java/lang/ProcessBuilder.java
@@ -64,7 +64,7 @@ import java.util.Map;
* working directory of the current process, usually the directory
* named by the system property {@code user.dir}.
*
- * <li><a name="redirect-input">a source of <i>standard input</i>.
+ * <li><a name="redirect-input">a source of <i>standard input</i></a>.
* By default, the subprocess reads input from a pipe. Java code
* can access this pipe via the output stream returned by
* {@link Process#getOutputStream()}. However, standard input may
@@ -80,7 +80,7 @@ import java.util.Map;
* </ul>
*
* <li><a name="redirect-output">a destination for <i>standard output</i>
- * and <i>standard error</i>. By default, the subprocess writes standard
+ * and <i>standard error</i></a>. By default, the subprocess writes standard
* output and standard error to pipes. Java code can access these pipes
* via the input streams returned by {@link Process#getInputStream()} and
* {@link Process#getErrorStream()}. However, standard output and
diff --git a/java/lang/reflect/AccessibleObject.java b/java/lang/reflect/AccessibleObject.java
index 08fc8cd6..79413767 100644
--- a/java/lang/reflect/AccessibleObject.java
+++ b/java/lang/reflect/AccessibleObject.java
@@ -54,6 +54,17 @@ import java.lang.annotation.Annotation;
*/
public class AccessibleObject implements AnnotatedElement {
+ // Android-removed: Code associated with SecurityManager calls.
+ /*
+ /**
+ * The Permission object that is used to check whether a client
+ * has sufficient privilege to defeat Java language access
+ * control checks.
+ *
+ static final private java.security.Permission ACCESS_PERMISSION =
+ new ReflectPermission("suppressAccessChecks");
+ */
+
/**
* Convenience method to set the {@code accessible} flag for an
* array of objects with a single security check (for efficiency).
@@ -81,6 +92,9 @@ public class AccessibleObject implements AnnotatedElement {
*/
public static void setAccessible(AccessibleObject[] array, boolean flag)
throws SecurityException {
+ // Android-removed: SecurityManager calls.
+ // SecurityManager sm = System.getSecurityManager();
+ // if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
for (int i = 0; i < array.length; i++) {
setAccessible0(array[i], flag);
}
@@ -112,6 +126,9 @@ public class AccessibleObject implements AnnotatedElement {
* @see java.lang.RuntimePermission
*/
public void setAccessible(boolean flag) throws SecurityException {
+ // Android-removed: SecurityManager calls.
+ // SecurityManager sm = System.getSecurityManager();
+ // if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
setAccessible0(this, flag);
}
@@ -122,7 +139,12 @@ public class AccessibleObject implements AnnotatedElement {
{
if (obj instanceof Constructor && flag == true) {
Constructor<?> c = (Constructor<?>)obj;
- // Android-changed: Added additional checks below.
+ // BEGIN Android-changed: Disallow making Method & Field constructors accessible.
+ // if (c.getDeclaringClass() == Class.class) {
+ // throw new SecurityException("Cannot make a java.lang.Class" +
+ // " constructor accessible");
+ // }
+
Class<?> clazz = c.getDeclaringClass();
if (c.getDeclaringClass() == Class.class) {
throw new SecurityException("Can not make a java.lang.Class" +
@@ -134,6 +156,7 @@ public class AccessibleObject implements AnnotatedElement {
throw new SecurityException("Can not make a java.lang.reflect.Field" +
" constructor accessible");
}
+ // END Android-changed: Disallow making Method & Field constructors accessible.
}
obj.override = flag;
}
@@ -160,6 +183,15 @@ public class AccessibleObject implements AnnotatedElement {
// outside this package.
boolean override;
+ /* Android-removed: reflectionFactory: it is not used on Android.
+ // Reflection factory used by subclasses for creating field,
+ // method, and constructor accessors. Note that this is called
+ // very early in the bootstrapping process.
+ static final ReflectionFactory reflectionFactory =
+ AccessController.doPrivileged(
+ new sun.reflect.ReflectionFactory.GetReflectionFactoryAction());
+ */
+
/**
* @throws NullPointerException {@inheritDoc}
* @since 1.5
@@ -224,4 +256,74 @@ public class AccessibleObject implements AnnotatedElement {
public Annotation[] getDeclaredAnnotations() {
throw new AssertionError("All subclasses should override this method");
}
+
+ // BEGIN Android-removed: Shared access checking logic: Not used on Android.
+ /*
+ // For non-public members or members in package-private classes,
+ // it is necessary to perform somewhat expensive security checks.
+ // If the security check succeeds for a given class, it will
+ // always succeed (it is not affected by the granting or revoking
+ // of permissions); we speed up the check in the common case by
+ // remembering the last Class for which the check succeeded.
+ //
+ // The simple security check for Constructor is to see if
+ // the caller has already been seen, verified, and cached.
+ // (See also Class.newInstance(), which uses a similar method.)
+ //
+ // A more complicated security check cache is needed for Method and Field
+ // The cache can be either null (empty cache), a 2-array of {caller,target},
+ // or a caller (with target implicitly equal to this.clazz).
+ // In the 2-array case, the target is always different from the clazz.
+ volatile Object securityCheckCache;
+
+ void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers)
+ throws IllegalAccessException
+ {
+ if (caller == clazz) { // quick check
+ return; // ACCESS IS OK
+ }
+ Object cache = securityCheckCache; // read volatile
+ Class<?> targetClass = clazz;
+ if (obj != null
+ && Modifier.isProtected(modifiers)
+ && ((targetClass = obj.getClass()) != clazz)) {
+ // Must match a 2-list of { caller, targetClass }.
+ if (cache instanceof Class[]) {
+ Class<?>[] cache2 = (Class<?>[]) cache;
+ if (cache2[1] == targetClass &&
+ cache2[0] == caller) {
+ return; // ACCESS IS OK
+ }
+ // (Test cache[1] first since range check for [1]
+ // subsumes range check for [0].)
+ }
+ } else if (cache == caller) {
+ // Non-protected case (or obj.class == this.clazz).
+ return; // ACCESS IS OK
+ }
+
+ // If no return, fall through to the slow path.
+ slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
+ }
+
+ // Keep all this slow stuff out of line:
+ void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers,
+ Class<?> targetClass)
+ throws IllegalAccessException
+ {
+ Reflection.ensureMemberAccess(caller, clazz, obj, modifiers);
+
+ // Success: Update the cache.
+ Object cache = ((targetClass == clazz)
+ ? caller
+ : new Class<?>[] { caller, targetClass });
+
+ // Note: The two cache elements are not volatile,
+ // but they are effectively final. The Java memory model
+ // guarantees that the initializing stores for the cache
+ // elements will occur before the volatile write.
+ securityCheckCache = cache; // write volatile
+ }
+ */
+ // END Android-removed: Shared access checking logic: Not used on Android.
}
diff --git a/java/lang/reflect/AnnotatedElement.java b/java/lang/reflect/AnnotatedElement.java
index a88d6c1e..cf6af474 100644
--- a/java/lang/reflect/AnnotatedElement.java
+++ b/java/lang/reflect/AnnotatedElement.java
@@ -31,7 +31,7 @@ import java.util.Objects;
import libcore.reflect.AnnotatedElements;
// Android-changed: Removed some references to bytecode spec below that do not
-// apply and added a note about annotation ordering.
+// apply to DEX and added a note about annotation ordering.
/**
* Represents an annotated element of the program currently running in this
* VM. This interface allows annotations to be read reflectively. All
@@ -316,6 +316,9 @@ public interface AnnotatedElement {
* @since 1.8
*/
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
+ // Android-changed: Altered method implementation for getAnnotationsByType(Class).
+ // Android's annotation code is customized because of the use of the DEX format on Android
+ // and code sharing with the runtime.
// This method does not handle inherited annotations and is intended for use for
// {@code Method}, {@code Field}, {@code Package}. The {@link Class#getAnnotationsByType}
// is implemented explicitly. Therefore this implementation does not fulfill the documented
@@ -402,6 +405,9 @@ public interface AnnotatedElement {
* @since 1.8
*/
default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
+ // Android-changed: Altered method implementation for getAnnotationsByType(Class).
+ // Android's annotation code is customized because of the use of the DEX format on Android
+ // and code sharing with the runtime.
return AnnotatedElements.getDirectOrIndirectAnnotationsByType(this, annotationClass);
}
diff --git a/java/lang/reflect/Array.java b/java/lang/reflect/Array.java
index 790a930b..95a80915 100644
--- a/java/lang/reflect/Array.java
+++ b/java/lang/reflect/Array.java
@@ -57,6 +57,7 @@ class Array {
* Array.newInstance(componentType, x);
* </pre>
* </blockquote>
+ *
* <p>The number of dimensions of the new array must not
* exceed 255.
*
@@ -110,6 +111,7 @@ class Array {
*/
public static Object newInstance(Class<?> componentType, int... dimensions)
throws IllegalArgumentException, NegativeArraySizeException {
+ // Android-changed: New implementation of newInstance(Class, int...)
if (dimensions.length <= 0 || dimensions.length > 255) {
throw new IllegalArgumentException("Bad number of dimensions: " + dimensions.length);
}
@@ -130,7 +132,10 @@ class Array {
* @exception IllegalArgumentException if the object argument is not
* an array
*/
- public static int getLength(Object array) {
+ // Android-changed: Non-native implementation of getLength(Object)
+ // Android-changed: Removal of explicit throws IllegalArgumentException from method signature.
+ public static int getLength(Object array)
+ /* throws IllegalArgumentException */ {
if (array instanceof Object[]) {
return ((Object[]) array).length;
} else if (array instanceof boolean[]) {
@@ -169,7 +174,9 @@ class Array {
* argument is negative, or if it is greater than or equal to the
* length of the specified array
*/
- public static Object get(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of get(Object, int)
+ public static Object get(Object array, int index)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof Object[]) {
return ((Object[]) array)[index];
}
@@ -219,7 +226,9 @@ class Array {
* length of the specified array
* @see Array#get
*/
- public static boolean getBoolean(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of getBoolean(Object, int)
+ public static boolean getBoolean(Object array, int index)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof boolean[]) {
return ((boolean[]) array)[index];
}
@@ -242,7 +251,9 @@ class Array {
* length of the specified array
* @see Array#get
*/
- public static byte getByte(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of getByte(Object, int)
+ public static byte getByte(Object array, int index)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof byte[]) {
return ((byte[]) array)[index];
}
@@ -265,7 +276,9 @@ class Array {
* length of the specified array
* @see Array#get
*/
- public static char getChar(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of getChar(Object, int)
+ public static char getChar(Object array, int index)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof char[]) {
return ((char[]) array)[index];
}
@@ -288,7 +301,9 @@ class Array {
* length of the specified array
* @see Array#get
*/
- public static short getShort(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of getShort(Object, int)
+ public static short getShort(Object array, int index)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof short[]) {
return ((short[]) array)[index];
} else if (array instanceof byte[]) {
@@ -313,7 +328,9 @@ class Array {
* length of the specified array
* @see Array#get
*/
- public static int getInt(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of getInt(Object, int)
+ public static int getInt(Object array, int index)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof int[]) {
return ((int[]) array)[index];
} else if (array instanceof byte[]) {
@@ -342,7 +359,9 @@ class Array {
* length of the specified array
* @see Array#get
*/
- public static long getLong(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of getLong(Object, int)
+ public static long getLong(Object array, int index)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof long[]) {
return ((long[]) array)[index];
} else if (array instanceof byte[]) {
@@ -373,7 +392,9 @@ class Array {
* length of the specified array
* @see Array#get
*/
- public static float getFloat(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of getFloat(Object, int)
+ public static float getFloat(Object array, int index)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof float[]) {
return ((float[]) array)[index];
} else if (array instanceof byte[]) {
@@ -406,7 +427,9 @@ class Array {
* length of the specified array
* @see Array#get
*/
- public static double getDouble(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of getDouble(Object, int)
+ public static double getDouble(Object array, int index)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof double[]) {
return ((double[]) array)[index];
} else if (array instanceof byte[]) {
@@ -442,7 +465,9 @@ class Array {
* argument is negative, or if it is greater than or equal to
* the length of the specified array
*/
- public static void set(Object array, int index, Object value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of set(Object, int, Object)
+ public static void set(Object array, int index, Object value)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (!array.getClass().isArray()) {
throw notAnArray(array);
}
@@ -481,7 +506,7 @@ class Array {
* object to the specified {@code boolean} value.
* @param array the array
* @param index the index into the array
- * @param value the new value of the indexed component
+ * @param z the new value of the indexed component
* @exception NullPointerException If the specified object argument
* is null
* @exception IllegalArgumentException If the specified object argument
@@ -493,10 +518,12 @@ class Array {
* the length of the specified array
* @see Array#set
*/
- // Android-changed param name s/z/value
- public static void setBoolean(Object array, int index, boolean value) {
+ // Android-changed: Non-native implementation of setBoolean(Object, int, boolean)
+ // Android-changed: Removal of explicit runtime exceptions throws clause
+ public static void setBoolean(Object array, int index, boolean z)
+ /* throws IllegalArgumentException, ArrayIndexOutOfBoundsException */ {
if (array instanceof boolean[]) {
- ((boolean[]) array)[index] = value;
+ ((boolean[]) array)[index] = z;
} else {
throw badArray(array);
}
@@ -507,7 +534,7 @@ class Array {
* object to the specified {@code byte} value.
* @param array the array
* @param index the index into the array
- * @param value the new value of the indexed component
+ * @param b the new value of the indexed component
* @exception NullPointerException If the specified object argument
* is null
* @exception IllegalArgumentException If the specified object argument
@@ -519,20 +546,21 @@ class Array {
* the length of the specified array
* @see Array#set
*/
- // Android-changed param name s/b/value
- public static void setByte(Object array, int index, byte value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of setByte(Object, int, byte)
+ public static void setByte(Object array, int index, byte b)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof byte[]) {
- ((byte[]) array)[index] = value;
+ ((byte[]) array)[index] = b;
} else if (array instanceof double[]) {
- ((double[]) array)[index] = value;
+ ((double[]) array)[index] = b;
} else if (array instanceof float[]) {
- ((float[]) array)[index] = value;
+ ((float[]) array)[index] = b;
} else if (array instanceof int[]) {
- ((int[]) array)[index] = value;
+ ((int[]) array)[index] = b;
} else if (array instanceof long[]) {
- ((long[]) array)[index] = value;
+ ((long[]) array)[index] = b;
} else if (array instanceof short[]) {
- ((short[]) array)[index] = value;
+ ((short[]) array)[index] = b;
} else {
throw badArray(array);
}
@@ -543,7 +571,7 @@ class Array {
* object to the specified {@code char} value.
* @param array the array
* @param index the index into the array
- * @param value the new value of the indexed component
+ * @param c the new value of the indexed component
* @exception NullPointerException If the specified object argument
* is null
* @exception IllegalArgumentException If the specified object argument
@@ -555,18 +583,19 @@ class Array {
* the length of the specified array
* @see Array#set
*/
- // Android-changed param name s/c/value
- public static void setChar(Object array, int index, char value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of setChar(Object, int, char)
+ public static void setChar(Object array, int index, char c)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof char[]) {
- ((char[]) array)[index] = value;
+ ((char[]) array)[index] = c;
} else if (array instanceof double[]) {
- ((double[]) array)[index] = value;
+ ((double[]) array)[index] = c;
} else if (array instanceof float[]) {
- ((float[]) array)[index] = value;
+ ((float[]) array)[index] = c;
} else if (array instanceof int[]) {
- ((int[]) array)[index] = value;
+ ((int[]) array)[index] = c;
} else if (array instanceof long[]) {
- ((long[]) array)[index] = value;
+ ((long[]) array)[index] = c;
} else {
throw badArray(array);
}
@@ -577,7 +606,7 @@ class Array {
* object to the specified {@code short} value.
* @param array the array
* @param index the index into the array
- * @param value the new value of the indexed component
+ * @param s the new value of the indexed component
* @exception NullPointerException If the specified object argument
* is null
* @exception IllegalArgumentException If the specified object argument
@@ -589,18 +618,19 @@ class Array {
* the length of the specified array
* @see Array#set
*/
- // Android-changed param name s/s/value
- public static void setShort(Object array, int index, short value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of setShort(Object, int, short)
+ public static void setShort(Object array, int index, short s)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof short[]) {
- ((short[]) array)[index] = value;
+ ((short[]) array)[index] = s;
} else if (array instanceof double[]) {
- ((double[]) array)[index] = value;
+ ((double[]) array)[index] = s;
} else if (array instanceof float[]) {
- ((float[]) array)[index] = value;
+ ((float[]) array)[index] = s;
} else if (array instanceof int[]) {
- ((int[]) array)[index] = value;
+ ((int[]) array)[index] = s;
} else if (array instanceof long[]) {
- ((long[]) array)[index] = value;
+ ((long[]) array)[index] = s;
} else {
throw badArray(array);
}
@@ -611,7 +641,7 @@ class Array {
* object to the specified {@code int} value.
* @param array the array
* @param index the index into the array
- * @param value the new value of the indexed component
+ * @param i the new value of the indexed component
* @exception NullPointerException If the specified object argument
* is null
* @exception IllegalArgumentException If the specified object argument
@@ -623,16 +653,17 @@ class Array {
* the length of the specified array
* @see Array#set
*/
- // Android-changed param name s/i/value
- public static void setInt(Object array, int index, int value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of setInt(Object, int, int)
+ public static void setInt(Object array, int index, int i)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof int[]) {
- ((int[]) array)[index] = value;
+ ((int[]) array)[index] = i;
} else if (array instanceof double[]) {
- ((double[]) array)[index] = value;
+ ((double[]) array)[index] = i;
} else if (array instanceof float[]) {
- ((float[]) array)[index] = value;
+ ((float[]) array)[index] = i;
} else if (array instanceof long[]) {
- ((long[]) array)[index] = value;
+ ((long[]) array)[index] = i;
} else {
throw badArray(array);
}
@@ -643,7 +674,7 @@ class Array {
* object to the specified {@code long} value.
* @param array the array
* @param index the index into the array
- * @param value the new value of the indexed component
+ * @param l the new value of the indexed component
* @exception NullPointerException If the specified object argument
* is null
* @exception IllegalArgumentException If the specified object argument
@@ -655,14 +686,15 @@ class Array {
* the length of the specified array
* @see Array#set
*/
- // Android-changed param name s/l/value
- public static void setLong(Object array, int index, long value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of setBoolean(Object, int, long)
+ public static void setLong(Object array, int index, long l)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof long[]) {
- ((long[]) array)[index] = value;
+ ((long[]) array)[index] = l;
} else if (array instanceof double[]) {
- ((double[]) array)[index] = value;
+ ((double[]) array)[index] = l;
} else if (array instanceof float[]) {
- ((float[]) array)[index] = value;
+ ((float[]) array)[index] = l;
} else {
throw badArray(array);
}
@@ -673,7 +705,7 @@ class Array {
* object to the specified {@code float} value.
* @param array the array
* @param index the index into the array
- * @param value the new value of the indexed component
+ * @param f the new value of the indexed component
* @exception NullPointerException If the specified object argument
* is null
* @exception IllegalArgumentException If the specified object argument
@@ -685,12 +717,12 @@ class Array {
* the length of the specified array
* @see Array#set
*/
- // Android-changed param name s/f/value
- public static void setFloat(Object array, int index, float value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ public static void setFloat(Object array, int index, float f)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof float[]) {
- ((float[]) array)[index] = value;
+ ((float[]) array)[index] = f;
} else if (array instanceof double[]) {
- ((double[]) array)[index] = value;
+ ((double[]) array)[index] = f;
} else {
throw badArray(array);
}
@@ -701,7 +733,7 @@ class Array {
* object to the specified {@code double} value.
* @param array the array
* @param index the index into the array
- * @param value the new value of the indexed component
+ * @param d the new value of the indexed component
* @exception NullPointerException If the specified object argument
* is null
* @exception IllegalArgumentException If the specified object argument
@@ -713,21 +745,21 @@ class Array {
* the length of the specified array
* @see Array#set
*/
- // Android-changed param name s/d/value
- public static void setDouble(Object array, int index, double value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+ // Android-changed: Non-native implementation of setDouble(Object, int, double)
+ public static void setDouble(Object array, int index, double d)
+ throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
if (array instanceof double[]) {
- ((double[]) array)[index] = value;
+ ((double[]) array)[index] = d;
} else {
throw badArray(array);
}
}
/*
- * Create a multi-dimensional array of objects with the specified type.
+ * Private
*/
- @FastNative
- private static native Object createMultiArray(Class<?> componentType, int[] dimensions) throws NegativeArraySizeException;
+ // Android-added: Added javadocs for newArray(Class, int)
/**
* Returns a new array of the specified component type and length.
* Equivalent to {@code new componentType[size]}.
@@ -737,36 +769,55 @@ class Array {
* @throws NegativeArraySizeException
* if {@code size < 0}
*/
- private static Object newArray(Class<?> componentType, int size) throws NegativeArraySizeException {
+ // Android-changed: Non-native implementation of newArray(Class, int)
+ private static Object newArray(Class<?> componentType, int length)
+ throws NegativeArraySizeException {
if (!componentType.isPrimitive()) {
- return createObjectArray(componentType, size);
+ return createObjectArray(componentType, length);
} else if (componentType == char.class) {
- return new char[size];
+ return new char[length];
} else if (componentType == int.class) {
- return new int[size];
+ return new int[length];
} else if (componentType == byte.class) {
- return new byte[size];
+ return new byte[length];
} else if (componentType == boolean.class) {
- return new boolean[size];
+ return new boolean[length];
} else if (componentType == short.class) {
- return new short[size];
+ return new short[length];
} else if (componentType == long.class) {
- return new long[size];
+ return new long[length];
} else if (componentType == float.class) {
- return new float[size];
+ return new float[length];
} else if (componentType == double.class) {
- return new double[size];
+ return new double[length];
} else if (componentType == void.class) {
throw new IllegalArgumentException("Can't allocate an array of void");
}
throw new AssertionError();
}
+ // Android-removed: multiNewArray(Class, int[]) method. createMultiArray used instead.
+ /*
+ private static native Object multiNewArray(Class<?> componentType,
+ int[] dimensions)
+ throws IllegalArgumentException, NegativeArraySizeException;
+ */
+
+ // Android-added: createMultiArray(Class, int[]) method. Used instead of multiNewArray
+ /*
+ * Create a multi-dimensional array of objects with the specified type.
+ */
+ @FastNative
+ private static native Object createMultiArray(Class<?> componentType, int[] dimensions)
+ throws NegativeArraySizeException;
+
+ // BEGIN Android-added: Helper methods to support custom method implementations.
/*
* Create a one-dimensional array of objects with the specified type.
*/
@FastNative
- private static native Object createObjectArray(Class<?> componentType, int length) throws NegativeArraySizeException;
+ private static native Object createObjectArray(Class<?> componentType, int length)
+ throws NegativeArraySizeException;
private static IllegalArgumentException notAnArray(Object o) {
throw new IllegalArgumentException("Not an array: " + o.getClass());
@@ -785,4 +836,5 @@ class Array {
throw incompatibleType(array);
}
}
+ // END Android-added: Helper methods to support custom method implementations.
}
diff --git a/java/text/DateFormatSymbols.java b/java/text/DateFormatSymbols.java
index 305f6f2a..946ab0d2 100644
--- a/java/text/DateFormatSymbols.java
+++ b/java/text/DateFormatSymbols.java
@@ -229,8 +229,9 @@ public class DateFormatSymbols implements Serializable, Cloneable {
* Unlocalized date-time pattern characters. For example: 'y', 'd', etc.
* All locales use the same these unlocalized pattern characters.
*/
- // Android-changed: Add 'c' (standalone day of week).
- static final String patternChars = "GyMdkHmsSEDFwWahKzZYuXLc";
+ // Android-changed: Add 'c' (standalone day of week), 'b' (day period),
+ // 'B' (flexible day period)
+ static final String patternChars = "GyMdkHmsSEDFwWahKzZYuXLcbB";
static final int PATTERN_ERA = 0; // G
static final int PATTERN_YEAR = 1; // y
@@ -257,6 +258,9 @@ public class DateFormatSymbols implements Serializable, Cloneable {
static final int PATTERN_MONTH_STANDALONE = 22; // L
// Android-added: Constant for standalone day of week.
static final int PATTERN_STANDALONE_DAY_OF_WEEK = 23; // c
+ // Android-added: Constant for pattern letter 'b', 'B'
+ static final int PATTERN_DAY_PERIOD = 24; // b
+ static final int PATTERN_FLEXIBLE_DAY_PERIOD = 25; // B
/**
* Localized date-time pattern characters. For example, a locale may
diff --git a/java/text/SimpleDateFormat.java b/java/text/SimpleDateFormat.java
index c294e5a2..dfbd1218 100644
--- a/java/text/SimpleDateFormat.java
+++ b/java/text/SimpleDateFormat.java
@@ -1077,7 +1077,11 @@ public class SimpleDateFormat extends DateFormat {
Calendar.ZONE_OFFSET,
Calendar.MONTH,
// Android-added: 'c' for standalone day of week.
- Calendar.DAY_OF_WEEK
+ Calendar.DAY_OF_WEEK,
+ // Android-added: Support for 'b'/'B' (day period). Calendar.AM_PM is just used as a
+ // placeholder in the absence of full support for day period.
+ Calendar.AM_PM,
+ Calendar.AM_PM
};
// Map index into pattern character string to DateFormat field number
@@ -1106,7 +1110,11 @@ public class SimpleDateFormat extends DateFormat {
DateFormat.TIMEZONE_FIELD,
DateFormat.MONTH_FIELD,
// Android-added: 'c' for standalone day of week.
- DateFormat.DAY_OF_WEEK_FIELD
+ DateFormat.DAY_OF_WEEK_FIELD,
+ // Android-added: Support for 'b'/'B' (day period). DateFormat.AM_PM_FIELD is just used as a
+ // placeholder in the absence of full support for day period.
+ DateFormat.AM_PM_FIELD,
+ DateFormat.AM_PM_FIELD
};
// Maps from DecimalFormatSymbols index to Field constant
@@ -1135,7 +1143,11 @@ public class SimpleDateFormat extends DateFormat {
Field.TIME_ZONE,
Field.MONTH,
// Android-added: 'c' for standalone day of week.
- Field.DAY_OF_WEEK
+ Field.DAY_OF_WEEK,
+ // Android-added: Support for 'b'/'B' (day period). Field.AM_PM is just used as a
+ // placeholder in the absence of full support for day period.
+ Field.AM_PM,
+ Field.AM_PM
};
// BEGIN Android-added: Special handling for UTC time zone.
@@ -1264,6 +1276,13 @@ public class SimpleDateFormat extends DateFormat {
}
break;
+ // Android-added: Ignore 'b' and 'B' introduced in CLDR 32+ pattern data. http://b/68139386
+ // Not currently supported here.
+ case PATTERN_DAY_PERIOD:
+ case PATTERN_FLEXIBLE_DAY_PERIOD:
+ current = "";
+ break;
+
case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM
if (current == null) {
if (value == 0) {
diff --git a/javax/obex/ServerOperation.java b/javax/obex/ServerOperation.java
index 56a675ac..15ea3678 100644
--- a/javax/obex/ServerOperation.java
+++ b/javax/obex/ServerOperation.java
@@ -195,7 +195,12 @@ public final class ServerOperation implements Operation, BaseStream {
if(!handleObexPacket(packet)) {
return;
}
- if (!mHasBody) {
+ /* Don't Pre-Send continue when Remote requested for SRM
+ * Let the Application confirm.
+ */
+ if (V) Log.v(TAG, "Get App confirmation if SRM ENABLED case: " + mSrmEnabled
+ + " not hasBody case: " + mHasBody);
+ if (!mHasBody && !mSrmEnabled) {
while ((!mGetOperation) && (!finalBitSet)) {
sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
if (mPrivateInput.available() > 0) {
@@ -204,8 +209,13 @@ public final class ServerOperation implements Operation, BaseStream {
}
}
}
-
- while ((!mGetOperation) && (!finalBitSet) && (mPrivateInput.available() == 0)) {
+ /* Don't Pre-Send continue when Remote requested for SRM
+ * Let the Application confirm.
+ */
+ if (V) Log.v(TAG, "Get App confirmation if SRM ENABLED case: " + mSrmEnabled
+ + " not finalPacket: " + finalBitSet + " not GETOp Case: " + mGetOperation);
+ while ((!mSrmEnabled) && (!mGetOperation) && (!finalBitSet)
+ && (mPrivateInput.available() == 0)) {
sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
if (mPrivateInput.available() > 0) {
break;